1212#include < private/qqmlopenmetaobject_p.h>
1313
1414#include < DConfig>
15+ #include < DThreadUtils>
1516
1617#ifndef QT_DEBUG
1718Q_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
112115void 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
131134void 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 */
169178QVariant 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
189202void 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
197216void 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 */
208261void 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