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>
{
public:
//! \copydoc BlackMisc::CCached::NotifySlot
template <typename T>
using NotifySlot = typename BlackMisc::CCached<typename Trait::type>::template NotifySlot<T>;
//! 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>
CData(T *owner, NotifySlot<T> slot = nullptr) :
CData::CCached(CDataCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner, slot)
CData(T *owner) :
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::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");
}
//! 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
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>
{
public:
//! \copydoc BlackMisc::CCached::NotifySlot
//! Constructor.
//! \param owner Will be the parent of the internal QObject used to access the value.
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.
//! \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>
CSetting(T *owner, NotifySlot<T> slot = nullptr) :
CSetting::CCached(CSettingsCache::instance(), Trait::key(), Trait::humanReadable(), Trait::isValid, Trait::defaultValue(), owner, slot)
{}
template <typename T, typename F>
CSetting(T *owner, F slot) : CSetting(owner)
{
this->setNotifySlot(slot);
}
//! Reset the setting to its default value.
CStatusMessage setDefault() { return this->set(Trait::defaultValue()); }

View File

@@ -538,8 +538,8 @@ namespace BlackMisc
struct CValuePage::Element
{
Element(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot) :
m_key(key), m_name(name), m_metaType(metaType), m_validator(validator), m_default(defaultValue), m_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)
{}
const QString m_key;
const QString m_name;
@@ -549,18 +549,18 @@ namespace BlackMisc
const int m_metaType = QMetaType::UnknownType;
const Validator m_validator;
const CVariant m_default;
const NotifySlot m_notifySlot = nullptr;
NotifySlot m_notifySlot;
int m_pendingChanges = 0;
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(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");
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);
auto status = validate(element, element.m_value.read(), CStatusMessage::SeverityDebug);
@@ -586,6 +586,11 @@ namespace BlackMisc
return element;
}
void CValuePage::setNotifySlot(Element &element, NotifySlot slot)
{
element.m_notifySlot = slot;
}
bool CValuePage::isValid(const Element &element, int typeId) const
{
auto reader = element.m_value.read();
@@ -667,7 +672,7 @@ namespace BlackMisc
Q_ASSERT(QThread::currentThread() == thread());
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)
{
@@ -684,9 +689,9 @@ namespace BlackMisc
element->m_value.uniqueWrite() = it.value();
element->m_timestamp = it.timestamp();
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
@@ -696,7 +701,7 @@ namespace BlackMisc
}
});
for (auto slot : notifySlots) { (parent()->*slot)(); }
for (auto slot : notifySlots) { (*slot)(parent()); }
}
void CValuePage::beginBatch()

View File

@@ -335,20 +335,14 @@ namespace BlackMisc
class CCached
{
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.
//! \param cache The CValueCache object which manages the value.
//! \param key The key string which identifies the value.
//! \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 slot A member function of owner which will be called when the value is changed by another source.
template <typename U>
CCached(CValueCache *cache, const QString &key, const QString &name, U *owner, NotifySlot<U> slot = nullptr) :
CCached(cache, key, name, nullptr, T{}, owner, slot)
CCached(CValueCache *cache, const QString &key, const QString &name, U *owner) :
CCached(cache, key, name, nullptr, T{}, owner)
{}
//! 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 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 slot A member function of owner which will be called when the value is changed by another source.
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_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);
}
//! 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.
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(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(); }
QVariant getVariantCopy() const { return m_page.getValueCopy(m_element).getQVariant(); }
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>
struct TIdentity
struct TClassOfPointerToMember
{
using type = QObject;
};
//! \cond
template <typename T, typename M>
struct TClassOfPointerToMember<M T::*>
{
using type = T;
};
/*!
* \private Trick to force a non-deduced context during template argument type deduction.
*/
template <typename T>
using TNonDeduced = typename TIdentity<T>::type;
//! \endcond
/*!
* \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.
using Validator = std::function<bool(const CVariant &)>;
//! Pointer-to-member-function type of slot to notify parent of changes.
using NotifySlot = void (QObject:: *)();
//! Functor used to notify parent of changes.
using NotifySlot = std::function<void(QObject *)>;
//! 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.
@@ -70,8 +71,10 @@ namespace BlackMisc
//! \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 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, NotifySlot slot);
Element &createElement(const QString &key, const QString &name, int metaType, Validator validator, const CVariant &defaultValue);
//! 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.
//! \threadsafe

View File

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