diff --git a/src/blackmisc/datacache.h b/src/blackmisc/datacache.h index f2e374220..7036b2ebf 100644 --- a/src/blackmisc/datacache.h +++ b/src/blackmisc/datacache.h @@ -309,6 +309,11 @@ namespace BlackMisc CData(T *owner) : CData::CCached(CDataCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner) { + if (! this->isInitialized()) + { + this->onOwnerNameChanged([this, owner] { Private::reconstruct(this, owner); }); + return; + } if (Trait::timeToLive() >= 0) { CDataCache::instance()->setTimeToLive(this->getKey(), Trait::timeToLive()); } if (Trait::isPinned()) { CDataCache::instance()->pinValue(this->getKey()); } if (Trait::isDeferred()) { CDataCache::instance()->deferValue(this->getKey()); } diff --git a/src/blackmisc/settingscache.h b/src/blackmisc/settingscache.h index ed085c298..fdf689fcf 100644 --- a/src/blackmisc/settingscache.h +++ b/src/blackmisc/settingscache.h @@ -74,7 +74,12 @@ namespace BlackMisc template CSetting(T *owner) : CSetting::CCached(CSettingsCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner) - {} + { + if (! this->isInitialized()) + { + this->onOwnerNameChanged([this, owner] { Private::reconstruct(this, owner); }); + } + } //! Constructor. //! \param owner Will be the parent of the internal QObject used to access the value. diff --git a/src/blackmisc/valuecache.cpp b/src/blackmisc/valuecache.cpp index f7e57c932..26586caca 100644 --- a/src/blackmisc/valuecache.cpp +++ b/src/blackmisc/valuecache.cpp @@ -48,7 +48,6 @@ namespace BlackMisc template bool isSafeToIncrement(const T &value) { return value < std::numeric_limits::max(); } - //! \private std::pair &> getCacheRootDirectoryMutable() { @@ -557,11 +556,16 @@ namespace BlackMisc CValuePage::Element &CValuePage::createElement(const QString &keyTemplate, const QString &name, int metaType, Validator validator, const CVariant &defaultValue) { - auto *category = parent()->findChild(); + if (parent()->objectName().isEmpty() && keyTemplate.contains("%OwnerName%")) + { + static Element dummy("", "", QMetaType::UnknownType, nullptr, {}); + return dummy; + } + QString key = keyTemplate; key.replace("%Application%", QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName(), Qt::CaseInsensitive); key.replace("%OwnerClass%", QString(parent()->metaObject()->className()).replace("::", "/"), Qt::CaseInsensitive); - key.replace("%OwnerCategory%", category ? category->getCategory() : QString(parent()->metaObject()->className()).replace("::", "/"), Qt::CaseInsensitive); + key.replace("%OwnerName%", parent()->objectName(), Qt::CaseInsensitive); Q_ASSERT_X(! m_elements.contains(key), "CValuePage", "Can't have two CCached in the same object referring to the same value"); Q_ASSERT_X(defaultValue.isValid() ? defaultValue.userType() == metaType : true, "CValuePage", "Metatype mismatch for default value"); @@ -598,6 +602,11 @@ namespace BlackMisc element.m_notifySlot = slot; } + bool CValuePage::isInitialized(const Element &element) const + { + return ! element.m_key.isEmpty(); + } + bool CValuePage::isValid(const Element &element, int typeId) const { auto reader = element.m_value.read(); @@ -606,6 +615,7 @@ namespace BlackMisc const CVariant &CValuePage::getValue(const Element &element) const { + Q_ASSERT_X(! element.m_key.isEmpty(), Q_FUNC_INFO, "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%"); Q_ASSERT(QThread::currentThread() == thread()); return element.m_value.read(); @@ -613,11 +623,13 @@ namespace BlackMisc CVariant CValuePage::getValueCopy(const Element &element) const { + Q_ASSERT_X(! element.m_key.isEmpty(), Q_FUNC_INFO, "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%"); return element.m_value.read(); } CStatusMessage CValuePage::setValue(Element &element, CVariant value, qint64 timestamp, bool save, bool ignoreValue) { + Q_ASSERT_X(! element.m_key.isEmpty(), Q_FUNC_INFO, "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%"); Q_ASSERT(QThread::currentThread() == thread()); if (timestamp == 0) { timestamp = QDateTime::currentMSecsSinceEpoch(); } @@ -661,6 +673,7 @@ namespace BlackMisc qint64 CValuePage::getTimestamp(const Element &element) const { + Q_ASSERT_X(! element.m_key.isEmpty(), Q_FUNC_INFO, "Empty key suggests an attempt to use value before objectName available for %%OwnerName%%"); return element.m_timestamp; } diff --git a/src/blackmisc/valuecache.h b/src/blackmisc/valuecache.h index 5a3e18588..0329ecc51 100644 --- a/src/blackmisc/valuecache.h +++ b/src/blackmisc/valuecache.h @@ -43,6 +43,7 @@ #include #include #include +#include namespace BlackMisc { @@ -357,7 +358,7 @@ namespace BlackMisc m_page(Private::CValuePage::getPageFor(owner, cache)), m_element(m_page.createElement(key, name, qMetaTypeId(), wrap(validator), CVariant::from(defaultValue))) { - cache->setHumanReadableName(key, name); + if (isInitialized()) { cache->setHumanReadableName(getKey(), name); } } //! Set a callback to be called when the value is changed by another source. @@ -365,6 +366,11 @@ namespace BlackMisc template void setNotifySlot(F slot) { + if (! isInitialized()) + { + onOwnerNameChanged([this, slot] { setNotifySlot(slot); }); + return; + } using U = typename Private::TClassOfPointerToMember::type; Q_ASSERT_X(m_page.parent()->inherits(U::staticMetaObject.className()), Q_FUNC_INFO, "Slot is member function of wrong class"); m_page.setNotifySlot(m_element, { [slot](QObject *obj) { Private::invokeSlot(slot, static_cast(obj)); }, makeId(slot) }); @@ -408,6 +414,9 @@ namespace BlackMisc //! Return true if this value is currently saving. bool isSaving() const { return m_page.isSaving(m_element); } + //! Can be false if key contains %OwnerName% and owner's objectName was empty. + bool isInitialized() const { return m_page.isInitialized(m_element); } + //! Deleted copy constructor. CCached(const CCached &) = delete; @@ -429,6 +438,17 @@ namespace BlackMisc bool isValid() const { return m_page.isValid(m_element, qMetaTypeId()); } protected: + //! \private Connect a function to be called (only once) when the owner's objectName changes. + void onOwnerNameChanged(std::function function) + { + auto connection = std::make_shared(); + *connection = QObject::connect(m_page.parent(), &QObject::objectNameChanged, [connection, function](const QString &) + { + QObject::disconnect(*connection); + function(); + }); + } + Private::CValuePage &m_page; //!< \private Private::CValuePage::Element &m_element; //!< \private }; diff --git a/src/blackmisc/valuecacheprivate.h b/src/blackmisc/valuecacheprivate.h index 59dfd965d..52d62fb4f 100644 --- a/src/blackmisc/valuecacheprivate.h +++ b/src/blackmisc/valuecacheprivate.h @@ -43,6 +43,16 @@ namespace BlackMisc }; //! \endcond + /*! + * \private Destroy an object and reconstruct it with the given constructor arguments. + */ + template + void reconstruct(T *object, Args &&... args) + { + object->~T(); + new (object) T(std::forward(args)...); + } + /*! * \private QObject subclass used by CCached class template for signal/slot communication with CValueCache. * An instance of this class is shared between all CCached referring to the same CValueCache and owned by the same QObject, @@ -76,6 +86,9 @@ namespace BlackMisc //! Set the functor to call to notify that the value corresponding to the element's key was modified. void setNotifySlot(Element &element, NotifySlot slot); + //! True if the currently paged value corresponds to a valid key. + bool isInitialized(const Element &element) const; + //! True if the currently paged value is a valid instance of the given type. //! \threadsafe bool isValid(const Element &element, int typeId) const;