refs #494 Using CValueCachePacket, cache values are given timestamps.

This commit is contained in:
Mathew Sutcliffe
2015-10-22 22:37:45 +01:00
parent eb11b69c6d
commit 966eed9044
13 changed files with 99 additions and 64 deletions

View File

@@ -462,7 +462,7 @@ namespace BlackMisc
{
if (it1.key() < it2.key()) { ++it1; }
else if (it2.key() < it1.key()) { ++it2; }
else { functor(it1.key(), it1.value(), it2.value()); ++it1; ++it2; }
else { functor(it1.key(), it1.value(), it2); ++it1; ++it2; }
}
}

View File

@@ -71,7 +71,7 @@ namespace BlackMisc
if (mode == LocalOnly)
{
// loopback signal to own slot for local operation
connect(this, &CValueCache::valuesChangedByLocal, this, [ = ](const CVariantMap &values)
connect(this, &CValueCache::valuesChangedByLocal, this, [ = ](const CValueCachePacket &values)
{
changeValuesFromRemote(values, CIdentifier());
});
@@ -84,6 +84,7 @@ namespace BlackMisc
const QString m_key;
CVariant m_value;
int m_pendingChanges = 0;
std::atomic<qint64> m_timestamp { QDateTime::currentMSecsSinceEpoch() };
};
CValueCache::Element &CValueCache::getElement(const QString &key)
@@ -100,10 +101,11 @@ namespace BlackMisc
return **m_elements.insert(pos, key, ElementPtr(new Element(key)));
}
CVariant CValueCache::getValue(const QString &key)
std::pair<CVariant, qint64> CValueCache::getValue(const QString &key)
{
QMutexLocker lock(&m_mutex);
return getElement(key).m_value;
const auto &element = getElement(key);
return std::make_pair(element.m_value, element.m_timestamp.load());
}
CVariantMap CValueCache::getAllValues(const QString &keyPrefix) const
@@ -117,13 +119,24 @@ namespace BlackMisc
return map;
}
void CValueCache::insertValues(const CVariantMap &values)
CValueCachePacket CValueCache::getAllValuesWithTimestamps(const QString &keyPrefix) const
{
QMutexLocker lock(&m_mutex);
CValueCachePacket map;
for (const auto &element : makeRange(m_elements.lowerBound(keyPrefix), m_elements.lowerBound(keyPrefix + QChar(QChar::LastValidCodePoint))))
{
map.insert(element->m_key, element->m_value, element->m_timestamp);
}
return map;
}
void CValueCache::insertValues(const CValueCachePacket &values)
{
QMutexLocker lock(&m_mutex);
changeValues(values);
}
void CValueCache::changeValues(const CVariantMap &values)
void CValueCache::changeValues(const CValueCachePacket &values)
{
QMutexLocker lock(&m_mutex);
if (values.empty()) { return; }
@@ -137,16 +150,17 @@ namespace BlackMisc
Q_ASSERT(isSafeToIncrement(element.m_pendingChanges));
element.m_pendingChanges++;
element.m_value = in.value();
element.m_timestamp = in.timestamp();
}
emit valuesChanged(values, sender());
emit valuesChangedByLocal(values);
}
void CValueCache::changeValuesFromRemote(const CVariantMap &values, const CIdentifier &originator)
void CValueCache::changeValuesFromRemote(const CValueCachePacket &values, const CIdentifier &originator)
{
QMutexLocker lock(&m_mutex);
if (values.empty()) { return; }
CVariantMap ratifiedChanges;
CValueCachePacket ratifiedChanges;
auto out = m_elements.lowerBound(values.cbegin().key());
auto end = m_elements.upperBound((values.cend() - 1).key());
for (auto in = values.cbegin(); in != values.cend(); ++in)
@@ -162,7 +176,8 @@ namespace BlackMisc
else if (element.m_pendingChanges == 0) // ratify a change only if own change is not pending, to ensure consistency
{
element.m_value = in.value();
ratifiedChanges.insert(in.key(), in.value());
element.m_timestamp = in.timestamp();
ratifiedChanges.insert(in.key(), in.value(), in.timestamp());
}
}
if (! ratifiedChanges.empty())
@@ -180,7 +195,7 @@ namespace BlackMisc
{
CVariantMap map;
map.convertFromJson(json);
insertValues(map);
insertValues({ map, QDateTime::currentMSecsSinceEpoch() });
}
CStatusMessage CValueCache::saveToFiles(const QString &dir, const QString &keyPrefix) const
@@ -229,13 +244,13 @@ namespace BlackMisc
CStatusMessage CValueCache::loadFromFiles(const QString &dir)
{
QMutexLocker lock(&m_mutex);
CVariantMap values;
CValueCachePacket values;
auto status = loadFromFiles(dir, values);
insertValues(values);
return status;
}
CStatusMessage CValueCache::loadFromFiles(const QString &dir, CVariantMap &o_values) const
CStatusMessage CValueCache::loadFromFiles(const QString &dir, CValueCachePacket &o_values) const
{
QMutexLocker lock(&m_mutex);
if (! QDir(dir).isReadable())
@@ -258,7 +273,7 @@ namespace BlackMisc
CVariantMap temp;
temp.convertFromJson(json.object());
temp.removeDuplicates(currentValues);
o_values.insert(temp);
o_values.insert(temp, QFileInfo(file).lastModified().toMSecsSinceEpoch());
}
return {};
}
@@ -309,6 +324,7 @@ namespace BlackMisc
{}
const QString m_key;
LockFree<CVariant> m_value;
std::atomic<qint64> m_timestamp { QDateTime::currentMSecsSinceEpoch() };
const int m_metaType = QMetaType::UnknownType;
const Validator m_validator;
const CVariant m_default;
@@ -323,7 +339,7 @@ namespace BlackMisc
Q_ASSERT_X(defaultValue.isValid() && validator ? validator(defaultValue) : true, "CValuePage", "Validator rejects default value");
auto &element = *(m_elements[key] = ElementPtr(new Element(key, metaType, validator, defaultValue, slot)));
element.m_value.uniqueWrite() = m_cache->getValue(key);
std::forward_as_tuple(element.m_value.uniqueWrite(), element.m_timestamp) = m_cache->getValue(key);
auto error = validate(element, element.m_value.read());
if (! error.isEmpty())
@@ -358,7 +374,7 @@ namespace BlackMisc
element.m_pendingChanges++;
element.m_value.uniqueWrite() = value;
emit valuesWantToCache({ { element.m_key, value } });
emit valuesWantToCache({ { { element.m_key, value } }, QDateTime::currentMSecsSinceEpoch() });
}
}
else
@@ -369,13 +385,18 @@ namespace BlackMisc
return error;
}
void CValuePage::setValuesFromCache(const CVariantMap &values, QObject *changedBy)
qint64 CValuePage::getTimestamp(const Element &element) const
{
return element.m_timestamp;
}
void CValuePage::setValuesFromCache(const CValueCachePacket &values, QObject *changedBy)
{
Q_ASSERT(QThread::currentThread() == thread());
QList<NotifySlot> notifySlots;
forEachIntersection(m_elements, values, [changedBy, this, &notifySlots](const QString &, const ElementPtr &element, const CVariant &value)
forEachIntersection(m_elements, values, [changedBy, this, &notifySlots](const QString &, const ElementPtr &element, CValueCachePacket::const_iterator it)
{
if (changedBy == this) // round trip
{
@@ -384,10 +405,11 @@ namespace BlackMisc
}
else if (element->m_pendingChanges == 0) // ratify a change only if own change is not pending, to ensure consistency
{
auto error = validate(*element, value);
auto error = validate(*element, it.value());
if (error.isEmpty())
{
element->m_value.uniqueWrite() = value;
element->m_value.uniqueWrite() = it.value();
element->m_timestamp = it.timestamp();
if (element->m_notifySlot && ! notifySlots.contains(element->m_notifySlot)) { notifySlots.push_back(element->m_notifySlot); }
}
else
@@ -426,13 +448,15 @@ namespace BlackMisc
if (m_batchMode <= 0 && ! m_batchedValues.empty())
{
forEachIntersection(m_elements, m_batchedValues, [](const QString &, const ElementPtr &element, const CVariant &value)
qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
forEachIntersection(m_elements, m_batchedValues, [timestamp](const QString &, const ElementPtr &element, CVariantMap::const_iterator it)
{
Q_ASSERT(isSafeToIncrement(element->m_pendingChanges));
element->m_pendingChanges++;
element->m_value.uniqueWrite() = value;
element->m_value.uniqueWrite() = it.value();
element->m_timestamp = timestamp;
});
emit valuesWantToCache(m_batchedValues);
emit valuesWantToCache({ m_batchedValues, timestamp });
}
}

View File

@@ -92,10 +92,14 @@ namespace BlackMisc
//! \threadsafe
BlackMisc::CVariantMap getAllValues(const QString &keyPrefix = {}) const;
//! Return map containing all values in the cache, and timestamps when they were modified.
//! \threadsafe
BlackMisc::CValueCachePacket getAllValuesWithTimestamps(const QString &keyPrefix = {}) const;
//! Add some values to the cache.
//! Values already in the cache will remain in the cache unless they are overwritten.
//! \threadsafe
void insertValues(const BlackMisc::CVariantMap &values);
void insertValues(const BlackMisc::CValueCachePacket &values);
//! Save values in Json format.
//! If prefix is provided then only those values whose keys start with that prefix.
@@ -131,20 +135,20 @@ namespace BlackMisc
//! \see BlackMisc::CValueCache::valuesChangedByLocal.
//! \param values The values that were changed.
//! \param originator Identifier of the process which made the change. Can be this very process, or a different one.
void changeValuesFromRemote(const BlackMisc::CVariantMap &values, const BlackMisc::CIdentifier &originator);
void changeValuesFromRemote(const BlackMisc::CValueCachePacket &values, const BlackMisc::CIdentifier &originator);
signals:
//! Emitted when values in the cache are changed by an object in this very process.
//! The interprocess communication (e.g. DBus) should arrange for this signal to call the slot changeValueFromRemote
//! of CValueCache instances in all processes including this one. The slot will do its own round-trip detection.
void valuesChangedByLocal(const BlackMisc::CVariantMap &values);
void valuesChangedByLocal(const BlackMisc::CValueCachePacket &values);
protected:
//! Save specific values to Json files in a given directory.
CStatusMessage saveToFiles(const QString &directory, const CVariantMap &values) const;
//! Load from Json files in a given directory any values which differ from the current ones, and insert them in o_values.
CStatusMessage loadFromFiles(const QString &directory, CVariantMap &o_values) const;
CStatusMessage loadFromFiles(const QString &directory, CValueCachePacket &o_values) const;
//! Mutex protecting operations which are critical on m_elements.
mutable QMutex m_mutex { QMutex::Recursive };
@@ -158,14 +162,14 @@ namespace BlackMisc
Element &getElement(const QString &key);
Element &getElement(const QString &key, QMap<QString, ElementPtr>::const_iterator pos);
CVariant getValue(const QString &key);
std::pair<CVariant, qint64> getValue(const QString &key);
signals:
//! \private
void valuesChanged(const BlackMisc::CVariantMap &values, QObject *changedBy);
void valuesChanged(const BlackMisc::CValueCachePacket &values, QObject *changedBy);
private slots:
void changeValues(const BlackMisc::CVariantMap &values);
void changeValues(const BlackMisc::CValueCachePacket &values);
};
/*!
@@ -211,6 +215,9 @@ namespace BlackMisc
//! Write a new value. Must be called from the thread in which the owner lives.
CStatusMessage set(const T &value) { return m_page.setValue(m_element, CVariant::from(value)); }
//! Return the time when this value was updated.
QDateTime getTimestamp() const { return m_page.getTimestamp(m_element); }
//! Deleted copy constructor.
CCached(const CCached &) = delete;

View File

@@ -22,6 +22,7 @@ namespace BlackMisc
{
class CIdentifier;
class CValueCache;
class CValueCachePacket;
namespace Private
{
@@ -63,11 +64,14 @@ namespace BlackMisc
//! Write the value corresponding to the element's key and begin synchronizing it to any other pages.
CStatusMessage setValue(Element &element, const CVariant &value);
//! Get the timestamp corresponding to the element.
qint64 getTimestamp(const Element &element) const;
//! Synchronize with a change caused by another page.
//! Connected to signal CValueCache::valuesChanged.
//! \param values The new values.
//! \param changedBy Pointer to the CValuePage which caused the change. Null if it was changed by another process.
void setValuesFromCache(const BlackMisc::CVariantMap &values, QObject *changedBy);
void setValuesFromCache(const BlackMisc::CValueCachePacket &values, QObject *changedBy);
//! Put this page into batching mode.
void beginBatch();
@@ -81,7 +85,7 @@ namespace BlackMisc
signals:
//! Synchronize this page's changes to other pages.
//! Connected to slot CValueCache::changeValues.
void valuesWantToCache(const BlackMisc::CVariantMap &values);
void valuesWantToCache(const BlackMisc::CValueCachePacket &values);
private:
using ElementPtr = QSharedPointer<Element>; // QMap doesn't support move-only types