refs #759 Allow cache value notification slot to be changed after construction.

Also allow slot to be any type of callable, including member function or lambda.
This commit is contained in:
Mathew Sutcliffe
2016-09-09 00:49:08 +01:00
committed by Roland Winklmeier
parent d24c17eba2
commit e01ae2be11
6 changed files with 71 additions and 52 deletions

View File

@@ -286,17 +286,11 @@ namespace BlackMisc
class CData : public BlackMisc::CCached<typename Trait::type> class CData : public BlackMisc::CCached<typename Trait::type>
{ {
public: public:
//! \copydoc BlackMisc::CCached::NotifySlot
template <typename T>
using NotifySlot = typename BlackMisc::CCached<typename Trait::type>::template NotifySlot<T>;
//! Constructor. //! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value. //! \param owner Will be the parent of the internal QObject used to access the value.
//! \param slot Slot to call when the value is modified by another object.
//! Must be a void, non-const member function of the owner.
template <typename T> template <typename T>
CData(T *owner, NotifySlot<T> slot = nullptr) : CData(T *owner) :
CData::CCached(CDataCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner, slot) CData::CCached(CDataCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner)
{ {
if (Trait::timeToLive() >= 0) { CDataCache::instance()->setTimeToLive(Trait::key(), Trait::timeToLive()); } if (Trait::timeToLive() >= 0) { CDataCache::instance()->setTimeToLive(Trait::key(), Trait::timeToLive()); }
if (Trait::isPinned()) { CDataCache::instance()->pinValue(Trait::key()); } if (Trait::isPinned()) { CDataCache::instance()->pinValue(Trait::key()); }
@@ -304,6 +298,16 @@ namespace BlackMisc
static_assert(! (Trait::isPinned() && Trait::isDeferred()), "trait can not be both pinned and deferred"); static_assert(! (Trait::isPinned() && Trait::isDeferred()), "trait can not be both pinned and deferred");
} }
//! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value.
//! \param slot Slot to call when the value is modified by another object.
//! Must be a void, non-const member function of the owner.
template <typename T, typename F>
CData(T *owner, F slot) : CData(owner)
{
this->setNotifySlot(slot);
}
//! \copydoc BlackMisc::CCached::set //! \copydoc BlackMisc::CCached::set
CStatusMessage set(const typename Trait::type &value, qint64 timestamp = 0) CStatusMessage set(const typename Trait::type &value, qint64 timestamp = 0)
{ {

View File

@@ -69,18 +69,22 @@ namespace BlackMisc
class CSetting : public BlackMisc::CCached<typename Trait::type> class CSetting : public BlackMisc::CCached<typename Trait::type>
{ {
public: public:
//! \copydoc BlackMisc::CCached::NotifySlot //! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value.
template <typename T> template <typename T>
using NotifySlot = typename BlackMisc::CCached<typename Trait::type>::template NotifySlot<T>; CSetting(T *owner) :
CSetting::CCached(CSettingsCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner)
{}
//! Constructor. //! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value. //! \param owner Will be the parent of the internal QObject used to access the value.
//! \param slot Slot to call when the value is modified by another object. //! \param slot Slot to call when the value is modified by another object.
//! Must be a void, non-const member function of the owner. //! Must be a void, non-const member function of the owner.
template <typename T> template <typename T, typename F>
CSetting(T *owner, NotifySlot<T> slot = nullptr) : CSetting(T *owner, F slot) : CSetting(owner)
CSetting::CCached(CSettingsCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner, slot) {
{} this->setNotifySlot(slot);
}
//! Reset the setting to its default value. //! Reset the setting to its default value.
CStatusMessage setDefault() { return this->set(Trait::defaultValue()); } CStatusMessage setDefault() { return this->set(Trait::defaultValue()); }

View File

@@ -538,8 +538,8 @@ namespace BlackMisc
struct CValuePage::Element struct CValuePage::Element
{ {
Element(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot) : Element(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue) :
m_key(key), m_name(name), m_metaType(metaType), m_validator(validator), m_default(defaultValue), m_notifySlot(slot) m_key(key), m_name(name), m_metaType(metaType), m_validator(validator), m_default(defaultValue)
{} {}
const QString m_key; const QString m_key;
const QString m_name; const QString m_name;
@@ -549,18 +549,18 @@ namespace BlackMisc
const int m_metaType = QMetaType::UnknownType; const int m_metaType = QMetaType::UnknownType;
const Validator m_validator; const Validator m_validator;
const CVariant m_default; const CVariant m_default;
const NotifySlot m_notifySlot = nullptr; NotifySlot m_notifySlot;
int m_pendingChanges = 0; int m_pendingChanges = 0;
bool m_saved = false; bool m_saved = false;
}; };
CValuePage::Element &CValuePage::createElement(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot) CValuePage::Element &CValuePage::createElement(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue)
{ {
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(! 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"); Q_ASSERT_X(defaultValue.isValid() ? defaultValue.userType() == metaType : true, "CValuePage", "Metatype mismatch for default value");
Q_ASSERT_X(defaultValue.isValid() && validator ? validator(defaultValue) : true, "CValuePage", "Validator rejects default value"); Q_ASSERT_X(defaultValue.isValid() && validator ? validator(defaultValue) : true, "CValuePage", "Validator rejects default value");
auto &element = *(m_elements[key] = ElementPtr(new Element(key, name, metaType, validator, defaultValue, slot))); auto &element = *(m_elements[key] = ElementPtr(new Element(key, name, metaType, validator, defaultValue)));
std::forward_as_tuple(element.m_value.uniqueWrite(), element.m_timestamp, element.m_saved) = m_cache->getValue(key); std::forward_as_tuple(element.m_value.uniqueWrite(), element.m_timestamp, element.m_saved) = m_cache->getValue(key);
auto status = validate(element, element.m_value.read(), CStatusMessage::SeverityDebug); auto status = validate(element, element.m_value.read(), CStatusMessage::SeverityDebug);
@@ -586,6 +586,11 @@ namespace BlackMisc
return element; return element;
} }
void CValuePage::setNotifySlot(Element &element, NotifySlot slot)
{
element.m_notifySlot = slot;
}
bool CValuePage::isValid(const Element &element, int typeId) const bool CValuePage::isValid(const Element &element, int typeId) const
{ {
auto reader = element.m_value.read(); auto reader = element.m_value.read();
@@ -667,7 +672,7 @@ namespace BlackMisc
Q_ASSERT(QThread::currentThread() == thread()); Q_ASSERT(QThread::currentThread() == thread());
Q_ASSERT_X(values.valuesChanged(), Q_FUNC_INFO, "packet with unchanged values should not reach here"); Q_ASSERT_X(values.valuesChanged(), Q_FUNC_INFO, "packet with unchanged values should not reach here");
QList<NotifySlot> notifySlots; QList<NotifySlot *> notifySlots;
forEachIntersection(m_elements, values, [changedBy, this, &notifySlots, &values](const QString &, const ElementPtr & element, CValueCachePacket::const_iterator it) forEachIntersection(m_elements, values, [changedBy, this, &notifySlots, &values](const QString &, const ElementPtr & element, CValueCachePacket::const_iterator it)
{ {
@@ -684,9 +689,9 @@ namespace BlackMisc
element->m_value.uniqueWrite() = it.value(); element->m_value.uniqueWrite() = it.value();
element->m_timestamp = it.timestamp(); element->m_timestamp = it.timestamp();
element->m_saved = values.isSaved(); element->m_saved = values.isSaved();
if (element->m_notifySlot && ! notifySlots.contains(element->m_notifySlot)) if (element->m_notifySlot && ! notifySlots.contains(&element->m_notifySlot))
{ {
notifySlots.push_back(element->m_notifySlot); notifySlots.push_back(&element->m_notifySlot);
} }
} }
else else
@@ -696,7 +701,7 @@ namespace BlackMisc
} }
}); });
for (auto slot : notifySlots) { (parent()->*slot)(); } for (auto slot : notifySlots) { (*slot)(parent()); }
} }
void CValuePage::beginBatch() void CValuePage::beginBatch()

View File

@@ -335,20 +335,14 @@ namespace BlackMisc
class CCached class CCached
{ {
public: public:
//! Type of pointer to non-const member function of U taking no arguments and returning void,
//! for slot parameter of CCached constructor.
template <typename U>
using NotifySlot = Private::TNonDeduced<void (U::*)()>;
//! Constructor. //! Constructor.
//! \param cache The CValueCache object which manages the value. //! \param cache The CValueCache object which manages the value.
//! \param key The key string which identifies the value. //! \param key The key string which identifies the value.
//! \param name Human readable name corresponding to the key. //! \param name Human readable name corresponding to the key.
//! \param owner Will be the parent of the internal QObject used for signal/slot connections. //! \param owner Will be the parent of the internal QObject used for signal/slot connections.
//! \param slot A member function of owner which will be called when the value is changed by another source.
template <typename U> template <typename U>
CCached(CValueCache *cache, const QString &key, const QString &name, U *owner, NotifySlot<U> slot = nullptr) : CCached(CValueCache *cache, const QString &key, const QString &name, U *owner) :
CCached(cache, key, name, nullptr, T{}, owner, slot) CCached(cache, key, name, nullptr, T{}, owner)
{} {}
//! Constructor. //! Constructor.
@@ -358,15 +352,24 @@ namespace BlackMisc
//! \param validator A functor which tests the validity of a value and returns true if it is valid. //! \param validator A functor which tests the validity of a value and returns true if it is valid.
//! \param defaultValue A value which will be used as default if the value is invalid. //! \param defaultValue A value which will be used as default if the value is invalid.
//! \param owner Will be the parent of the internal QObject used for signal/slot connections. //! \param owner Will be the parent of the internal QObject used for signal/slot connections.
//! \param slot A member function of owner which will be called when the value is changed by another source.
template <typename U, typename F> template <typename U, typename F>
CCached(CValueCache *cache, const QString &key, const QString &name, F validator, const T &defaultValue, U *owner, NotifySlot<U> slot = nullptr) : CCached(CValueCache *cache, const QString &key, const QString &name, F validator, const T &defaultValue, U *owner) :
m_page(Private::CValuePage::getPageFor(owner, cache)), m_page(Private::CValuePage::getPageFor(owner, cache)),
m_element(m_page.createElement(key, name, qMetaTypeId<T>(), wrap(validator), CVariant::from(defaultValue), slot_cast(slot))) m_element(m_page.createElement(key, name, qMetaTypeId<T>(), wrap(validator), CVariant::from(defaultValue)))
{ {
cache->setHumanReadableName(key, name); cache->setHumanReadableName(key, name);
} }
//! Set a callback to be called when the value is changed by another source.
//! \todo Qt 5.7.0: in assert use m_page.parent()->metaObject()->inherits(&U::staticMetaObject)
template <typename F>
void setNotifySlot(F slot)
{
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)); });
}
//! Read the current value. //! Read the current value.
const T &getThreadLocal() const { static const T empty {}; return *(isValid() ? static_cast<const T *>(getVariant().data()) : &empty); } const T &getThreadLocal() const { static const T empty {}; return *(isValid() ? static_cast<const T *>(getVariant().data()) : &empty); }
@@ -416,9 +419,6 @@ namespace BlackMisc
static Private::CValuePage::Validator wrap(F func) { return [func](const CVariant &value)->bool { return func(value.to<T>()); }; } static Private::CValuePage::Validator wrap(F func) { return [func](const CVariant &value)->bool { return func(value.to<T>()); }; }
static Private::CValuePage::Validator wrap(std::nullptr_t) { return {}; } static Private::CValuePage::Validator wrap(std::nullptr_t) { return {}; }
template <typename F>
static Private::CValuePage::NotifySlot slot_cast(F slot) { return static_cast<Private::CValuePage::NotifySlot>(slot); }
const QVariant &getVariant() const { return m_page.getValue(m_element).getQVariant(); } const QVariant &getVariant() const { return m_page.getValue(m_element).getQVariant(); }
QVariant getVariantCopy() const { return m_page.getValueCopy(m_element).getQVariant(); } QVariant getVariantCopy() const { return m_page.getValueCopy(m_element).getQVariant(); }
bool isValid() const { return m_page.isValid(m_element, qMetaTypeId<T>()); } bool isValid() const { return m_page.isValid(m_element, qMetaTypeId<T>()); }

View File

@@ -28,19 +28,20 @@ namespace BlackMisc
{ {
/*! /*!
* \private Identity type trait. * \private Trait for obtaining the class type of a pointer to member type, or QObject if T is not a pointer to member.
*/ */
template <typename T> template <typename T>
struct TIdentity struct TClassOfPointerToMember
{
using type = QObject;
};
//! \cond
template <typename T, typename M>
struct TClassOfPointerToMember<M T::*>
{ {
using type = T; using type = T;
}; };
//! \endcond
/*!
* \private Trick to force a non-deduced context during template argument type deduction.
*/
template <typename T>
using TNonDeduced = typename TIdentity<T>::type;
/*! /*!
* \private QObject subclass used by CCached<T> class template for signal/slot communication with CValueCache. * \private QObject subclass used by CCached<T> class template for signal/slot communication with CValueCache.
@@ -61,8 +62,8 @@ namespace BlackMisc
//! Functor used for validating values. //! Functor used for validating values.
using Validator = std::function<bool(const CVariant &)>; using Validator = std::function<bool(const CVariant &)>;
//! Pointer-to-member-function type of slot to notify parent of changes. //! Functor used to notify parent of changes.
using NotifySlot = void (QObject:: *)(); using NotifySlot = std::function<void(QObject *)>;
//! Returns a new instance of the opaque Element type for use by CCached<T> to interact with CValuePage. //! Returns a new instance of the opaque Element type for use by CCached<T> to interact with CValuePage.
//! \param key The key string of the value in the cache. //! \param key The key string of the value in the cache.
@@ -70,8 +71,10 @@ namespace BlackMisc
//! \param metaType The Qt metatype ID of the value object's expected type. //! \param metaType The Qt metatype ID of the value object's expected type.
//! \param validator Optional functor which returns true if the value is valid. //! \param validator Optional functor which returns true if the value is valid.
//! \param defaultValue Optional value which is used in case the value is invalid. //! \param defaultValue Optional value which is used in case the value is invalid.
//! \param slot Optional member function of parent, will be called when value changes. Element &createElement(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue);
Element &createElement(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot);
//! 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 is a valid instance of the given type. //! True if the currently paged value is a valid instance of the given type.
//! \threadsafe //! \threadsafe

View File

@@ -270,9 +270,12 @@ namespace BlackMiscTest
} }
CValueCacheUser::CValueCacheUser(CValueCache *cache) : CValueCacheUser::CValueCacheUser(CValueCache *cache) :
m_value1(cache, "value1", "", validator, 0, this, &CValueCacheUser::ps_valueChanged), m_value1(cache, "value1", "", validator, 0, this),
m_value2(cache, "value2", "", validator, 0, this, &CValueCacheUser::ps_valueChanged) m_value2(cache, "value2", "", validator, 0, this)
{} {
m_value1.setNotifySlot(&CValueCacheUser::ps_valueChanged);
m_value2.setNotifySlot(&CValueCacheUser::ps_valueChanged);
}
void CValueCacheUser::ps_valueChanged() void CValueCacheUser::ps_valueChanged()
{ {