refs #684, #766, #776 Support %OwnerName% in cache keys by allowing CCached::m_element to be a dummy

and by using a trick with explicit destructor call and placement new to reconstruct CCached when owner's name changes.
This commit is contained in:
Mathew Sutcliffe
2016-10-15 20:37:00 +01:00
parent 9acfb89e65
commit 8a65a33384
5 changed files with 61 additions and 5 deletions

View File

@@ -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()); }

View File

@@ -74,7 +74,12 @@ namespace BlackMisc
template <typename T>
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.

View File

@@ -48,7 +48,6 @@ namespace BlackMisc
template <typename T>
bool isSafeToIncrement(const T &value) { return value < std::numeric_limits<T>::max(); }
//! \private
std::pair<QString &, std::atomic<bool> &> 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<CValueCacheCategory *>();
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;
}

View File

@@ -43,6 +43,7 @@
#include <cstddef>
#include <tuple>
#include <utility>
#include <memory>
namespace BlackMisc
{
@@ -357,7 +358,7 @@ namespace BlackMisc
m_page(Private::CValuePage::getPageFor(owner, cache)),
m_element(m_page.createElement(key, name, qMetaTypeId<T>(), 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 <typename F>
void setNotifySlot(F slot)
{
if (! isInitialized())
{
onOwnerNameChanged([this, slot] { setNotifySlot(slot); });
return;
}
using U = typename Private::TClassOfPointerToMember<F>::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<U *>(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<T>()); }
protected:
//! \private Connect a function to be called (only once) when the owner's objectName changes.
void onOwnerNameChanged(std::function<void()> function)
{
auto connection = std::make_shared<QMetaObject::Connection>();
*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
};

View File

@@ -43,6 +43,16 @@ namespace BlackMisc
};
//! \endcond
/*!
* \private Destroy an object and reconstruct it with the given constructor arguments.
*/
template <typename T, typename... Args>
void reconstruct(T *object, Args &&... args)
{
object->~T();
new (object) T(std::forward<Args>(args)...);
}
/*!
* \private QObject subclass used by CCached<T> class template for signal/slot communication with CValueCache.
* An instance of this class is shared between all CCached<T> 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;