Skip to content

Commit 7d37100

Browse files
committed
feat: using DConfig in the non-GUI thread
The DConfig is a sync IO API, call it's API maybe block the GUI thread on the DConfigWrapper, so we need use the DConfig in a new thread.
1 parent 80796ab commit 7d37100

File tree

6 files changed

+232
-56
lines changed

6 files changed

+232
-56
lines changed

src/private/dconfigwrapper.cpp

Lines changed: 201 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <private/qqmlopenmetaobject_p.h>
1313

1414
#include <DConfig>
15+
#include <DThreadUtils>
1516

1617
#ifndef QT_DEBUG
1718
Q_LOGGING_CATEGORY(cfLog, "dtk.dsg.config" , QtInfoMsg);
@@ -31,7 +32,8 @@ static DefalutProperties propertyAndValues(const QObject* obj)
3132
const int count = mo->propertyCount();
3233
static const QStringList ReservedPropertyNames {
3334
"name",
34-
"subpath"
35+
"subpath",
36+
"async"
3537
};
3638

3739
for (int i = offset; i < count; ++i) {
@@ -63,7 +65,7 @@ class DConfigWrapperMetaObject : public QQmlOpenMetaObject {
6365
{
6466
const QByteArray &proName = name(index);
6567
qCDebug(cfLog) << "propertyWriteValue" << proName << value;
66-
owner->impl->setValue(proName, value);
68+
owner->setValue(proName, value);
6769
// Pre judgment returns the set value first.
6870
// If the value is different, `valueChanged` will be triggered again to update the value,
6971
// there are problems when the service is unavailable.
@@ -73,7 +75,8 @@ class DConfigWrapperMetaObject : public QQmlOpenMetaObject {
7375
int metaCall(QObject *o, QMetaObject::Call _c, int _id, void **_a) override
7476
{
7577
if (_c == QMetaObject::ResetProperty) {
76-
owner->impl->reset(name(_id - type()->propertyOffset()));
78+
const auto key = name(_id - type()->propertyOffset());
79+
owner->resetValue(key);
7780
}
7881

7982
return QQmlOpenMetaObject::metaCall(o, _c, _id, _a);
@@ -111,8 +114,8 @@ QString DConfigWrapper::name() const
111114

112115
void DConfigWrapper::setName(const QString &name)
113116
{
114-
if (!m_name.isEmpty()) {
115-
qWarning() << "name is existed." << m_name;
117+
if (mo) {
118+
qCWarning(cfLog) << name << ": This name can't be changed after initialized";
116119
return;
117120
}
118121

@@ -130,8 +133,8 @@ QString DConfigWrapper::subpath() const
130133

131134
void DConfigWrapper::setSubpath(const QString &subpath)
132135
{
133-
if (!m_subpath.isEmpty()) {
134-
qWarning() << "subpath is existed." << m_subpath;
136+
if (mo) {
137+
qCWarning(cfLog) << subpath << ": This subpath can't be changed after initialized";
135138
return;
136139
}
137140

@@ -147,7 +150,7 @@ QStringList DConfigWrapper::keyList() const
147150
if (!impl)
148151
return QStringList();
149152

150-
return impl->keyList();
153+
return configKeyList;
151154
}
152155

153156
/*!
@@ -159,7 +162,13 @@ bool DConfigWrapper::isValid() const
159162
if (!impl)
160163
return false;
161164

162-
return impl->isValid();
165+
// If is invalid, will delete the impl object
166+
return true;
167+
}
168+
169+
bool DConfigWrapper::isDefaultValue(const QString &key) const
170+
{
171+
return !nonDefaultValueKeyList.contains(key);
163172
}
164173

165174
/*!
@@ -168,10 +177,8 @@ bool DConfigWrapper::isValid() const
168177
*/
169178
QVariant DConfigWrapper::value(const QString &key, const QVariant &fallback) const
170179
{
171-
if (!impl)
172-
return fallback;
173-
174-
return impl->value(key, fallback);
180+
const auto &value = property(key.toLatin1().constData());
181+
return value.isValid() ? value : fallback;
175182
}
176183

177184
/*!
@@ -183,22 +190,68 @@ void DConfigWrapper::setValue(const QString &key, const QVariant &value)
183190
if (!impl)
184191
return;
185192

186-
impl->setValue(key, value);
193+
if (m_async) {
194+
QMetaObject::invokeMethod(impl.get(), [this, key, value] {
195+
impl->setValue(key, value);
196+
});
197+
} else {
198+
impl->setValue(key, value);
199+
}
187200
}
188201

189202
void DConfigWrapper::resetValue(const QString &key)
190203
{
191204
if (!impl)
192205
return;
193206

194-
impl->reset(key);
207+
if (m_async) {
208+
QMetaObject::invokeMethod(impl.get(), [this, key] {
209+
impl->reset(key);
210+
});
211+
} else {
212+
impl->reset(key);
213+
}
195214
}
196215

197216
void DConfigWrapper::classBegin()
198217
{
199218

200219
}
201220

221+
class Q_DECL_HIDDEN DConfigWrapperThread : public QThread {
222+
public:
223+
DConfigWrapperThread()
224+
: QThread()
225+
{
226+
setObjectName("DConfigWrapperThread");
227+
moveToThread(this);
228+
}
229+
~DConfigWrapperThread() override
230+
{
231+
quit();
232+
wait();
233+
}
234+
};
235+
236+
static QThread *globalThread() {
237+
static QThread *thread = nullptr;
238+
if (!thread) {
239+
thread = new DConfigWrapperThread();
240+
thread->start();
241+
}
242+
return thread;
243+
}
244+
245+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
246+
DTK_CORE_NAMESPACE::DThreadUtils *globalThreadUtils() {
247+
static DTK_CORE_NAMESPACE::DThreadUtils *threadUtils = nullptr;
248+
if (!threadUtils) {
249+
threadUtils = new DTK_CORE_NAMESPACE::DThreadUtils(globalThread());
250+
}
251+
return threadUtils;
252+
}
253+
#endif
254+
202255
/*!
203256
\brief Initialize `DConfig` and redirect method of property's get and set.
204257
`DConfig` can only be initialized after \property name and \property subpath initialization
@@ -207,51 +260,149 @@ void DConfigWrapper::classBegin()
207260
*/
208261
void DConfigWrapper::componentComplete()
209262
{
210-
impl = new DTK_CORE_NAMESPACE::DConfig(m_name, m_subpath, this);
263+
Q_ASSERT(!impl);
264+
265+
// Get the dynamic properties and previous values defined in qml.
266+
// Muse before new DConfigWrapperMetaObject
267+
initializeConfigs = propertyAndValues(this);
268+
qCDebug(cfLog) << "Initialize Properties:" << initializeConfigs;
269+
270+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
271+
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject);
272+
#else
273+
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject, qmlEngine(this));
274+
#endif
275+
276+
mo = new DConfigWrapperMetaObject(this, objectType);
277+
mo->setCached(true);
278+
279+
if (m_async) {
280+
// Init properties
281+
for (auto iter = initializeConfigs.begin(); iter != initializeConfigs.end(); iter++) {
282+
mo->setValue(iter.key(), iter.value());
283+
}
284+
285+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
286+
globalThreadUtils()->run(this, &DConfigWrapper::initializeProperties);
287+
#else
288+
QMetaObject::invokeMethod(globalThread(), [this] {
289+
initializeProperties();
290+
});
291+
#endif
292+
} else {
293+
initializeProperties();
294+
}
295+
}
296+
297+
template<typename Fun, typename... Args>
298+
typename std::result_of<typename std::decay<Fun>::type(Args...)>::type
299+
callInGuiThread(DConfigWrapper *wrapper, Fun fun, Args&&... args) {
300+
if (QThread::currentThread() == qApp->thread()) {
301+
return fun(std::forward<Args>(args)...);
302+
}
303+
304+
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
305+
return DThreadUtils::gui().exec(wrapper, fun, std::forward<Args>(args)...);
306+
#else
307+
return DThreadUtil::runInMainThread(wrapper, fun, std::forward<Args>(args)...);
308+
#endif
309+
}
310+
311+
// in config thread, don't set the member variable directly.
312+
// Must ensure the member variable is set in the main thread.
313+
// So there have a "const" flag.
314+
void DConfigWrapper::initializeProperties() const
315+
{
316+
auto impl = new DTK_CORE_NAMESPACE::DConfig(m_name, m_subpath);
211317

212318
if (!impl->isValid()) {
213319
qCWarning(cfLog) << QString("create dconfig failed, valid:%1, name:%2, subpath:%3, backend:%4")
214-
.arg(impl->isValid())
215-
.arg(impl->name())
216-
.arg(impl->subpath())
217-
.arg(impl->backendName());
218-
impl->deleteLater();
320+
.arg(impl->isValid())
321+
.arg(impl->name())
322+
.arg(impl->subpath())
323+
.arg(impl->backendName());
324+
delete impl;
219325
impl = nullptr;
220326
return;
221327
}
222328

223329
qInfo() << QString("create dconfig successful, valid:%1, name:%2, subpath:%3, backend:%4")
224-
.arg(impl->isValid())
225-
.arg(impl->name())
226-
.arg(impl->subpath())
227-
.arg(impl->backendName());
330+
.arg(impl->isValid())
331+
.arg(impl->name())
332+
.arg(impl->subpath())
333+
.arg(impl->backendName());
334+
335+
const auto keyList = impl->keyList();
336+
QStringList nonDefaultValueKeyList;
337+
for (const auto &key : keyList) {
338+
if (!impl->isDefaultValue(key)) {
339+
nonDefaultValueKeyList.append(key);
340+
}
341+
}
228342

229-
// Get the dynamic properties and previous values defined in qml.
230-
const DefalutProperties &properties = propertyAndValues(this);
231-
qCDebug(cfLog) << "properties" << properties;
343+
auto wrapper = const_cast<DConfigWrapper*>(this);
344+
callInGuiThread(wrapper, [wrapper, keyList, nonDefaultValueKeyList, impl] {
345+
wrapper->impl.reset(impl);
346+
wrapper->configKeyList = keyList;
347+
wrapper->nonDefaultValueKeyList = nonDefaultValueKeyList;
348+
});
349+
350+
for (const auto &key : keyList) {
351+
const QVariant currentValue = callInGuiThread(wrapper, [wrapper, key] {
352+
return wrapper->property(key.toLocal8Bit());
353+
});
354+
355+
const auto initialValue = initializeConfigs.value(key.toLocal8Bit());
356+
if (currentValue.isValid() && currentValue != initialValue) {
357+
// This key has been set value in QML by user, so we should update the value to DConfig.
358+
qCDebug(cfLog) << "Update value from user on initialize, key:" << key
359+
<< "value:" << currentValue << "initialize value:" << initialValue
360+
<< "config side value:" << impl->value(key, QVariant());
361+
impl->setValue(key, currentValue);
362+
} else {
363+
// Must fallback to the initial value, in the sync mode, the DConfigWrapperMetaObject's
364+
// properties is not initialize.
365+
const auto value = impl->value(key, initialValue);
366+
callInGuiThread(wrapper, [wrapper, key, value] {
367+
if (value.isValid())
368+
wrapper->mo->setValue(key.toLocal8Bit(), value);
369+
});
370+
}
371+
}
232372

233-
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
234-
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject, qmlEngine(this));
235-
#else
236-
auto objectType = new QQmlOpenMetaObjectType(&DConfigWrapper::staticMetaObject);
237-
#endif
238-
auto mo = new DConfigWrapperMetaObject(this, objectType);
239-
mo->setCached(true);
373+
// Using QueuedConnection because impl->setValue maybe emit sync signal in `propertyWriteValue`.
374+
connect(impl, &DTK_CORE_NAMESPACE::DConfig::valueChanged, wrapper, [wrapper, impl](const QString &key) {
375+
const QByteArray &propName = key.toLocal8Bit();
376+
377+
qCDebug(cfLog) << "update value from DConfig by 'valueChanged', key:" << propName;
378+
const auto value = impl->value(propName, QVariant());
379+
const bool isDefault = impl->isDefaultValue(propName);
380+
callInGuiThread(wrapper, [wrapper, propName, value, isDefault] {
381+
if (isDefault) {
382+
wrapper->nonDefaultValueKeyList.removeOne(propName);
383+
} else if (!wrapper->nonDefaultValueKeyList.contains(propName)) {
384+
wrapper->nonDefaultValueKeyList.append(propName);
385+
}
386+
if (value.isValid())
387+
wrapper->mo->setValue(propName, value);
388+
});
389+
390+
QMetaObject::invokeMethod(wrapper, [wrapper, key] {
391+
Q_EMIT wrapper->valueChanged(key);
392+
});
393+
}, Qt::DirectConnection);
394+
395+
QMetaObject::invokeMethod(wrapper, &DConfigWrapper::initialized);
396+
}
240397

241-
for (auto iter = properties.begin(); iter != properties.end(); iter++) {
242-
// it's need to emit signal, because other qml object maybe read the old value
243-
// when binding the property before the component completed, also it has a performance problem.
244-
// sync backend's value to `Wrapper`, we only use Wrapper's value(defined in qml) as fallback value.
245-
mo->setValue(iter.key(), impl->value(iter.key(), iter.value()));
246-
}
398+
bool DConfigWrapper::async() const
399+
{
400+
return m_async;
401+
}
247402

248-
// Using QueuedConnection because impl->setValue maybe emit sync signal in `propertyWriteValue`.
249-
connect(impl, &DTK_CORE_NAMESPACE::DConfig::valueChanged, this, [this, mo, properties](const QString &key){
250-
const QByteArray &proName = key.toLocal8Bit();
251-
if (properties.contains(proName)) {
252-
qCDebug(cfLog) << "update value from DConfig by 'valueChanged', key:" << proName;
253-
mo->setValue(proName, impl->value(proName, properties.value(proName)));
254-
}
255-
Q_EMIT valueChanged(key);
256-
}, Qt::QueuedConnection);
403+
void DConfigWrapper::setAsync(bool newAsync)
404+
{
405+
m_async = newAsync;
406+
if (mo)
407+
qCWarning(cfLog) << "Async can't be changed after initialized";
257408
}

0 commit comments

Comments
 (0)