mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-05-02 15:15:39 +08:00
refs #297 Added the kernel of the distributed value cache system.
This commit is contained in:
333
src/blackmisc/valuecache.cpp
Normal file
333
src/blackmisc/valuecache.cpp
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
/* Copyright (C) 2015
|
||||||
|
* swift project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||||
|
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||||
|
* contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "blackmisc/valuecache.h"
|
||||||
|
#include "blackmisc/identifier.h"
|
||||||
|
#include "blackmisc/logmessage.h"
|
||||||
|
#include "blackmisc/algorithm.h"
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
namespace BlackMisc
|
||||||
|
{
|
||||||
|
|
||||||
|
using Private::CValuePage;
|
||||||
|
|
||||||
|
// Used in asserts to protect against signed integer overflow.
|
||||||
|
template <typename T>
|
||||||
|
bool isSafeToIncrement(const T &value) { return value < std::numeric_limits<T>::max(); }
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// CValueCache
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
CValueCache::CValueCache(CValueCache::DistributionMode mode, QObject *parent) :
|
||||||
|
QObject(parent)
|
||||||
|
{
|
||||||
|
if (mode == LocalOnly)
|
||||||
|
{
|
||||||
|
// loopback signal to own slot for local operation
|
||||||
|
connect(this, &CValueCache::valuesChangedByLocal, this, [ = ](const CVariantMap &values)
|
||||||
|
{
|
||||||
|
changeValuesFromRemote(values, CIdentifier());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CValueCache::Element
|
||||||
|
{
|
||||||
|
Element(const QString &key) : m_key(key) {}
|
||||||
|
const QString m_key;
|
||||||
|
CVariant m_value;
|
||||||
|
int m_pendingChanges = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
CValueCache::Element &CValueCache::getElement(const QString &key)
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
return getElement(key, m_elements.lowerBound(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
CValueCache::Element &CValueCache::getElement(const QString &key, QMap<QString, ElementPtr>::const_iterator pos)
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
if (pos != m_elements.end() && pos.key() == key) { return **pos; }
|
||||||
|
Q_ASSERT(pos == m_elements.lowerBound(key));
|
||||||
|
return **m_elements.insert(pos, key, ElementPtr(new Element(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
CVariant CValueCache::getValue(const QString &key)
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
return getElement(key).m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
CVariantMap CValueCache::getAllValues(const QString &keyPrefix) const
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
auto begin = m_elements.lowerBound(keyPrefix);
|
||||||
|
auto end = m_elements.lowerBound(keyPrefix + QChar(QChar::LastValidCodePoint));
|
||||||
|
CVariantMap map;
|
||||||
|
for (auto it = begin; it != end; ++it) { implementationOf(map).insert(map.cend(), it.key(), it.value()->m_value); }
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValueCache::insertValues(const CVariantMap &values)
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
changeValues(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValueCache::changeValues(const CVariantMap &values)
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
if (values.empty()) { return; }
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
while (out != end && out.key() < in.key()) { ++out; }
|
||||||
|
auto &element = getElement(in.key(), out);
|
||||||
|
|
||||||
|
Q_ASSERT(isSafeToIncrement(element.m_pendingChanges));
|
||||||
|
element.m_pendingChanges++;
|
||||||
|
element.m_value = in.value();
|
||||||
|
}
|
||||||
|
emit valuesChanged(values, sender());
|
||||||
|
emit valuesChangedByLocal(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValueCache::changeValuesFromRemote(const CVariantMap &values, const CIdentifier &originator)
|
||||||
|
{
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
if (values.empty()) { return; }
|
||||||
|
CVariantMap 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)
|
||||||
|
{
|
||||||
|
while (out != end && out.key() < in.key()) { ++out; }
|
||||||
|
auto &element = getElement(in.key(), out);
|
||||||
|
|
||||||
|
if (originator.isFromSameProcess()) // round trip
|
||||||
|
{
|
||||||
|
element.m_pendingChanges--;
|
||||||
|
Q_ASSERT(element.m_pendingChanges >= 0);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! ratifiedChanges.empty())
|
||||||
|
{
|
||||||
|
emit valuesChanged(ratifiedChanges, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject CValueCache::saveToJson(const QString &keyPrefix) const
|
||||||
|
{
|
||||||
|
return getAllValues(keyPrefix).toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValueCache::loadFromJson(const QJsonObject &json)
|
||||||
|
{
|
||||||
|
CVariantMap map;
|
||||||
|
map.convertFromJson(json);
|
||||||
|
insertValues(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
CValueCache::BatchGuard CValueCache::batchChanges(QObject *owner)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == owner->thread());
|
||||||
|
|
||||||
|
auto &page = CValuePage::getPageFor(owner, this);
|
||||||
|
page.beginBatch();
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
CValueCache::BatchGuard::~BatchGuard() Q_DECL_NOEXCEPT
|
||||||
|
{
|
||||||
|
if (m_page)
|
||||||
|
{
|
||||||
|
if (std::uncaught_exception()) { m_page->abandonBatch(); }
|
||||||
|
else { m_page->endBatch(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Private :: CValuePage
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
CValuePage::CValuePage(QObject *parent, CValueCache *cache) :
|
||||||
|
QObject(parent),
|
||||||
|
m_cache(cache)
|
||||||
|
{
|
||||||
|
connect(this, &CValuePage::valuesWantToCache, cache, &CValueCache::changeValues);
|
||||||
|
connect(cache, &CValueCache::valuesChanged, this, &CValuePage::setValuesFromCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
CValuePage &CValuePage::getPageFor(QObject *parent, CValueCache *cache)
|
||||||
|
{
|
||||||
|
auto pages = parent->findChildren<CValuePage *>();
|
||||||
|
auto it = std::find_if(pages.cbegin(), pages.cend(), [cache](CValuePage *page) { return page->m_cache == cache; });
|
||||||
|
if (it == pages.cend()) { return *new CValuePage(parent, cache); }
|
||||||
|
else { return **it; }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CValuePage::Element
|
||||||
|
{
|
||||||
|
Element(const QString &key, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot) :
|
||||||
|
m_key(key), m_metaType(metaType), m_validator(validator), m_default(defaultValue), m_notifySlot(slot)
|
||||||
|
{}
|
||||||
|
const QString m_key;
|
||||||
|
CVariant m_value;
|
||||||
|
const int m_metaType = QMetaType::UnknownType;
|
||||||
|
const Validator m_validator;
|
||||||
|
const CVariant m_default;
|
||||||
|
const NotifySlot m_notifySlot = nullptr;
|
||||||
|
int m_pendingChanges = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
CValuePage::Element &CValuePage::createElement(const QString &key, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot)
|
||||||
|
{
|
||||||
|
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, metaType, validator, defaultValue, slot)));
|
||||||
|
element.m_value = m_cache->getValue(key);
|
||||||
|
|
||||||
|
auto error = validate(element, element.m_value);
|
||||||
|
if (! error.isEmpty()) { element.m_value = defaultValue; }
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CVariant &CValuePage::getValue(const Element &element) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
return element.m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
CStatusMessage CValuePage::setValue(Element &element, const CVariant &value)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
if (element.m_value == value) { return {}; }
|
||||||
|
|
||||||
|
auto error = validate(element, value);
|
||||||
|
if (error.isEmpty())
|
||||||
|
{
|
||||||
|
if (m_batchMode > 0)
|
||||||
|
{
|
||||||
|
m_batchedValues[element.m_key] = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Q_ASSERT(isSafeToIncrement(element.m_pendingChanges));
|
||||||
|
element.m_pendingChanges++;
|
||||||
|
|
||||||
|
element.m_value = value;
|
||||||
|
emit valuesWantToCache({ { element.m_key, value } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValuePage::setValuesFromCache(const CVariantMap &values, QObject *changedBy)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
QList<NotifySlot> notifySlots;
|
||||||
|
|
||||||
|
forEachIntersection(m_elements, values, [changedBy, this, ¬ifySlots](const QString &, const ElementPtr &element, const CVariant &value)
|
||||||
|
{
|
||||||
|
if (changedBy == this) // round trip
|
||||||
|
{
|
||||||
|
element->m_pendingChanges--;
|
||||||
|
Q_ASSERT(element->m_pendingChanges >= 0);
|
||||||
|
}
|
||||||
|
else if (element->m_pendingChanges == 0) // ratify a change only if own change is not pending, to ensure consistency
|
||||||
|
{
|
||||||
|
auto error = validate(*element, value);
|
||||||
|
if (error.isEmpty())
|
||||||
|
{
|
||||||
|
element->m_value = value;
|
||||||
|
if (element->m_notifySlot && ! notifySlots.contains(element->m_notifySlot)) { notifySlots.push_back(element->m_notifySlot); }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLogMessage::preformatted(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto slot : notifySlots) { (parent()->*slot)(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValuePage::beginBatch()
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
Q_ASSERT(isSafeToIncrement(m_batchMode));
|
||||||
|
if (m_batchMode <= 0) { m_batchedValues.clear(); }
|
||||||
|
m_batchMode++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValuePage::abandonBatch()
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
Q_ASSERT(m_batchMode >= 0);
|
||||||
|
m_batchMode--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CValuePage::endBatch()
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
Q_ASSERT(m_batchMode >= 0);
|
||||||
|
m_batchMode--;
|
||||||
|
|
||||||
|
if (m_batchMode <= 0 && ! m_batchedValues.empty())
|
||||||
|
{
|
||||||
|
forEachIntersection(m_elements, m_batchedValues, [](const QString &, const ElementPtr &element, const CVariant &value)
|
||||||
|
{
|
||||||
|
Q_ASSERT(isSafeToIncrement(element->m_pendingChanges));
|
||||||
|
element->m_pendingChanges++;
|
||||||
|
element->m_value = value;
|
||||||
|
});
|
||||||
|
emit valuesWantToCache(m_batchedValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CStatusMessage CValuePage::validate(const Element &element, const CVariant &value) const
|
||||||
|
{
|
||||||
|
if (! value.isValid())
|
||||||
|
{
|
||||||
|
return CLogMessage(this).warning("Uninitialized value for %1") << element.m_key;
|
||||||
|
}
|
||||||
|
else if (value.userType() != element.m_metaType)
|
||||||
|
{
|
||||||
|
return CLogMessage(this).error("Expected %1, got %2 for %3") << QMetaType::typeName(element.m_metaType) << value.typeName() << element.m_key;
|
||||||
|
}
|
||||||
|
else if (element.m_validator && ! element.m_validator(value))
|
||||||
|
{
|
||||||
|
return CLogMessage(this).error("%1 is not valid for %2") << value << element.m_key;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
196
src/blackmisc/valuecache.h
Normal file
196
src/blackmisc/valuecache.h
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/* Copyright (C) 2015
|
||||||
|
* swift project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||||
|
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||||
|
* contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! \file
|
||||||
|
|
||||||
|
#ifndef BLACKMISC_VALUECACHE_H
|
||||||
|
#define BLACKMISC_VALUECACHE_H
|
||||||
|
|
||||||
|
#include "blackmisc/valuecache_private.h"
|
||||||
|
|
||||||
|
namespace BlackMisc
|
||||||
|
{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Manages a map of { QString, CVariant } pairs, which can be distributed among multiple processes.
|
||||||
|
*/
|
||||||
|
class BLACKMISC_EXPORT CValueCache : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
class BatchGuard;
|
||||||
|
|
||||||
|
//! Whether or not the cache can be distributed among multiple processes.
|
||||||
|
enum DistributionMode
|
||||||
|
{
|
||||||
|
LocalOnly, //!< Not distributed.
|
||||||
|
Distributed //!< Distributed among multiple processes.
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Constructor.
|
||||||
|
//! \param mode Whether or not the cache can be distributed among multiple processes.
|
||||||
|
//! \param parent The parent of the QObject.
|
||||||
|
explicit CValueCache(DistributionMode mode, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
//! Return map containing all values in the cache.
|
||||||
|
//! If prefix is provided then only those values whose keys start with that prefix.
|
||||||
|
//! \threadsafe
|
||||||
|
BlackMisc::CVariantMap getAllValues(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);
|
||||||
|
|
||||||
|
//! Save values in Json format.
|
||||||
|
//! If prefix is provided then only those values whose keys start with that prefix.
|
||||||
|
//! \threadsafe
|
||||||
|
QJsonObject saveToJson(const QString &keyPrefix = {}) const;
|
||||||
|
|
||||||
|
//! Load all values in Json format.
|
||||||
|
//! Values already in the cache will remain in the cache unless they are overwritten.
|
||||||
|
//! \threadsafe
|
||||||
|
void loadFromJson(const QJsonObject &json);
|
||||||
|
|
||||||
|
//! Begins a batch of changes to be made through CCached instances owned by owner.
|
||||||
|
//! \details All changes made through those CCached instances will be deferred until the returned RAII object is
|
||||||
|
//! destroyed. If the destruction happens during stack unwinding due to an exception being thrown, the changes are
|
||||||
|
//! abandoned, otherwise they are applied in one single change signal instead of lots of individual ones.
|
||||||
|
//! Can be called multiple times; the batch will be applied (or abandoned) when the last RAII object is destroyed.
|
||||||
|
//! CCached::getValue will continue to return the old value until the batched changes are applied.
|
||||||
|
//! \note Must be called in the thread where owner lives.
|
||||||
|
BatchGuard batchChanges(QObject *owner);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
//! Notify this cache that values have been changed by one of the duplicate caches in the multi-process environment.
|
||||||
|
//! \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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Private::CValuePage;
|
||||||
|
struct Element;
|
||||||
|
using ElementPtr = QSharedPointer<Element>; // QMap doesn't support move-only types
|
||||||
|
|
||||||
|
QMap<QString, ElementPtr> m_elements;
|
||||||
|
mutable QMutex m_mutex { QMutex::Recursive };
|
||||||
|
|
||||||
|
Element &getElement(const QString &key);
|
||||||
|
Element &getElement(const QString &key, QMap<QString, ElementPtr>::const_iterator pos);
|
||||||
|
CVariant getValue(const QString &key);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
//! \private
|
||||||
|
void valuesChanged(const BlackMisc::CVariantMap &values, QObject *changedBy);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void changeValues(const BlackMisc::CVariantMap &values);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Provides access to one of the values stored in a CValueCache.
|
||||||
|
* \tparam T The expected value type of the cached value.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
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 = void (U::*)();
|
||||||
|
|
||||||
|
//! Constructor.
|
||||||
|
//! \param cache The CValueCache object which manages the value.
|
||||||
|
//! \param key The key string which identifies the value.
|
||||||
|
//! \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, U *owner, NotifySlot<U> slot = nullptr) :
|
||||||
|
CCached(cache, key, nullptr, T{}, owner, slot)
|
||||||
|
{}
|
||||||
|
|
||||||
|
//! Constructor.
|
||||||
|
//! \param cache The CValueCache object which manages the value.
|
||||||
|
//! \param key The key string which identifies the value.
|
||||||
|
//! \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, F validator, const T &defaultValue, U *owner, NotifySlot<U> slot = nullptr) :
|
||||||
|
m_page(Private::CValuePage::getPageFor(owner, cache)),
|
||||||
|
m_element(m_page.createElement(key, qMetaTypeId<T>(), wrap(validator), CVariant::from(defaultValue), slot_cast(slot)))
|
||||||
|
{}
|
||||||
|
|
||||||
|
//! Read the current value. Must be called from the thread in which the owner lives.
|
||||||
|
const T &get() const { static const T empty {}; return *(isValid() ? static_cast<const T *>(getVariant().data()) : &empty); }
|
||||||
|
|
||||||
|
//! 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)); }
|
||||||
|
|
||||||
|
//! Deleted copy constructor.
|
||||||
|
CCached(const CCached &) = delete;
|
||||||
|
|
||||||
|
//! Deleted copy assignment operator.
|
||||||
|
CCached &operator =(const CCached &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename F>
|
||||||
|
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(); }
|
||||||
|
bool isValid() const { return getVariant().isValid() && getVariant().userType() == qMetaTypeId<T>(); }
|
||||||
|
|
||||||
|
Private::CValuePage &m_page;
|
||||||
|
Private::CValuePage::Element &m_element;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* RAII object returned by CValueCache::batchChanges. Applies deferred changes when it is destroyed.
|
||||||
|
*/
|
||||||
|
class BLACKMISC_EXPORT CValueCache::BatchGuard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//! Destructor. Applies deferred changes.
|
||||||
|
~BatchGuard() Q_DECL_NOEXCEPT;
|
||||||
|
|
||||||
|
//! Deleted copy constructor. Class is move-only.
|
||||||
|
BatchGuard(const BatchGuard &) = delete;
|
||||||
|
|
||||||
|
//! Deleted copy assignment operator. Class is move-only.
|
||||||
|
BatchGuard &operator =(const BatchGuard &) = delete;
|
||||||
|
|
||||||
|
//! Move constructor.
|
||||||
|
BatchGuard(BatchGuard &&other) : m_page(other.m_page) { other.m_page = nullptr; }
|
||||||
|
|
||||||
|
//! Move assignment operator.
|
||||||
|
BatchGuard &operator =(BatchGuard &&other) { std::swap(m_page, other.m_page); return *this; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class CValueCache;
|
||||||
|
BatchGuard(Private::CValuePage &page) : m_page(&page) {}
|
||||||
|
Private::CValuePage *m_page;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
101
src/blackmisc/valuecache_private.h
Normal file
101
src/blackmisc/valuecache_private.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/* Copyright (C) 2015
|
||||||
|
* swift project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||||
|
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||||
|
* contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! \file
|
||||||
|
|
||||||
|
#ifndef BLACKMISC_VALUECACHE_PRIVATE_H
|
||||||
|
#define BLACKMISC_VALUECACHE_PRIVATE_H
|
||||||
|
|
||||||
|
#include "blackmisc/variantmap.h"
|
||||||
|
#include "blackmisc/statusmessage.h"
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
namespace BlackMisc
|
||||||
|
{
|
||||||
|
class CIdentifier;
|
||||||
|
class CValueCache;
|
||||||
|
|
||||||
|
namespace Private
|
||||||
|
{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \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,
|
||||||
|
* with the latter QObject becoming parent of this instance.
|
||||||
|
*/
|
||||||
|
class BLACKMISC_EXPORT CValuePage : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Returns a CValuePage owned by the parent and connected to the cache, creating one if it doesn't exist.
|
||||||
|
static CValuePage &getPageFor(QObject *parent, CValueCache *cache);
|
||||||
|
|
||||||
|
//! Opaque type holding the state of one value in the page.
|
||||||
|
struct Element;
|
||||||
|
|
||||||
|
//! 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:: *)();
|
||||||
|
|
||||||
|
//! 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 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, int metaType, Validator validator, const CVariant &defaultValue, NotifySlot slot);
|
||||||
|
|
||||||
|
//! Read the currently paged value corresponding to the element's key.
|
||||||
|
const CVariant &getValue(const Element &element) const;
|
||||||
|
|
||||||
|
//! Write the value corresponding to the element's key and begin synchronizing it to any other pages.
|
||||||
|
CStatusMessage setValue(Element &element, const CVariant &value);
|
||||||
|
|
||||||
|
//! 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);
|
||||||
|
|
||||||
|
//! Put this page into batching mode.
|
||||||
|
void beginBatch();
|
||||||
|
|
||||||
|
//! Take this page out of batching mode and discard changes.
|
||||||
|
void abandonBatch();
|
||||||
|
|
||||||
|
//! Take this page out of batching mode and apply changes.
|
||||||
|
void endBatch();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
//! Synchronize this page's changes to other pages.
|
||||||
|
//! Connected to slot CValueCache::changeValues.
|
||||||
|
void valuesWantToCache(const BlackMisc::CVariantMap &values);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using ElementPtr = QSharedPointer<Element>; // QMap doesn't support move-only types
|
||||||
|
|
||||||
|
CValueCache *m_cache = nullptr;
|
||||||
|
QMap<QString, ElementPtr> m_elements;
|
||||||
|
int m_batchMode = 0;
|
||||||
|
CVariantMap m_batchedValues;
|
||||||
|
|
||||||
|
CValuePage(QObject *parent, CValueCache *cache);
|
||||||
|
CStatusMessage validate(const Element &element, const CVariant &value) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "testcontainers.h"
|
#include "testcontainers.h"
|
||||||
#include "testvariantandmap.h"
|
#include "testvariantandmap.h"
|
||||||
#include "testhardware.h"
|
#include "testhardware.h"
|
||||||
|
#include "testvaluecache.h"
|
||||||
#include "testblackmiscmain.h"
|
#include "testblackmiscmain.h"
|
||||||
|
|
||||||
namespace BlackMiscTest
|
namespace BlackMiscTest
|
||||||
@@ -32,6 +33,7 @@ namespace BlackMiscTest
|
|||||||
CTestVariantAndMap variantAndMap;
|
CTestVariantAndMap variantAndMap;
|
||||||
CTestHardware hardwareTests;
|
CTestHardware hardwareTests;
|
||||||
CTestIdentifier identifierTests;
|
CTestIdentifier identifierTests;
|
||||||
|
CTestValueCache valueCacheTests;
|
||||||
status |= QTest::qExec(&pqBaseTests, argc, argv);
|
status |= QTest::qExec(&pqBaseTests, argc, argv);
|
||||||
status |= QTest::qExec(&avBaseTests, argc, argv);
|
status |= QTest::qExec(&avBaseTests, argc, argv);
|
||||||
status |= QTest::qExec(&geoTests, argc, argv);
|
status |= QTest::qExec(&geoTests, argc, argv);
|
||||||
@@ -39,7 +41,7 @@ namespace BlackMiscTest
|
|||||||
status |= QTest::qExec(&containerTests, argc, argv);
|
status |= QTest::qExec(&containerTests, argc, argv);
|
||||||
status |= QTest::qExec(&variantAndMap, argc, argv);
|
status |= QTest::qExec(&variantAndMap, argc, argv);
|
||||||
status |= QTest::qExec(&hardwareTests, argc, argv);
|
status |= QTest::qExec(&hardwareTests, argc, argv);
|
||||||
status |= QTest::qExec(&hardwareTests, argc, argv);
|
status |= QTest::qExec(&valueCacheTests, argc, argv);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|||||||
225
tests/blackmisc/testvaluecache.cpp
Normal file
225
tests/blackmisc/testvaluecache.cpp
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
/* Copyright (C) 2015
|
||||||
|
* swift project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||||
|
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||||
|
* contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "testvaluecache.h"
|
||||||
|
#include "blackmisc/worker.h"
|
||||||
|
#include "blackmisc/identifier.h"
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
namespace BlackMiscTest
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace BlackMisc;
|
||||||
|
|
||||||
|
CTestValueCache::CTestValueCache(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
CVariant::registerMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestValueCache::insertAndGet()
|
||||||
|
{
|
||||||
|
CVariantMap testData
|
||||||
|
{
|
||||||
|
{ "value1", CVariant::from(1) },
|
||||||
|
{ "value2", CVariant::from(2) },
|
||||||
|
{ "value3", CVariant::from(3) }
|
||||||
|
};
|
||||||
|
CVariantMap testData2
|
||||||
|
{
|
||||||
|
{ "value2", CVariant::from(42) },
|
||||||
|
{ "value4", CVariant::from(4) }
|
||||||
|
};
|
||||||
|
CVariantMap testDataCombined
|
||||||
|
{
|
||||||
|
{ "value1", CVariant::from(1) },
|
||||||
|
{ "value2", CVariant::from(42) },
|
||||||
|
{ "value3", CVariant::from(3) },
|
||||||
|
{ "value4", CVariant::from(4) }
|
||||||
|
};
|
||||||
|
|
||||||
|
CValueCache cache(CValueCache::LocalOnly);
|
||||||
|
QVERIFY(cache.getAllValues() == CVariantMap());
|
||||||
|
cache.insertValues(testData);
|
||||||
|
QVERIFY(cache.getAllValues() == testData);
|
||||||
|
cache.insertValues(testData2);
|
||||||
|
QVERIFY(cache.getAllValues() == testDataCombined);
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForQueueOf(QObject *object)
|
||||||
|
{
|
||||||
|
if (object->thread() != QThread::currentThread())
|
||||||
|
{
|
||||||
|
std::promise<void> promise;
|
||||||
|
QTimer::singleShot(0, object, [ & ] { promise.set_value(); });
|
||||||
|
promise.get_future().wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void singleShotAndWait(QObject *object, F task)
|
||||||
|
{
|
||||||
|
if (object->thread() == QThread::currentThread())
|
||||||
|
{
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTimer::singleShot(0, object, task);
|
||||||
|
waitForQueueOf(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void testCommon(CValueCacheUser &user1, CValueCacheUser &user2)
|
||||||
|
{
|
||||||
|
user1.m_value1.set(42);
|
||||||
|
QVERIFY(user2.slotFired());
|
||||||
|
QVERIFY(! user1.slotFired());
|
||||||
|
singleShotAndWait(&user2, [ & ] { QVERIFY(user2.m_value1.get() == 42); });
|
||||||
|
QVERIFY(user1.m_value1.get() == 42);
|
||||||
|
|
||||||
|
user1.m_value2.set(42);
|
||||||
|
user2.slotFired();
|
||||||
|
QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("-1337 is not valid"));
|
||||||
|
user1.m_value2.set(-1337);
|
||||||
|
QVERIFY(! user1.slotFired());
|
||||||
|
QVERIFY(! user2.slotFired());
|
||||||
|
singleShotAndWait(&user2, [ & ] { QVERIFY(user2.m_value2.get() == 42); });
|
||||||
|
QVERIFY(user1.m_value2.get() == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestValueCache::localOnly()
|
||||||
|
{
|
||||||
|
CValueCache cache(CValueCache::LocalOnly);
|
||||||
|
for (int i = 0; i < 4; ++i) { QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Uninitialized value")); }
|
||||||
|
CValueCacheUser user1(&cache);
|
||||||
|
CValueCacheUser user2(&cache);
|
||||||
|
testCommon(user1, user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestValueCache::localOnlyWithThreads()
|
||||||
|
{
|
||||||
|
CValueCache cache(CValueCache::LocalOnly);
|
||||||
|
for (int i = 0; i < 4; ++i) { QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Uninitialized value")); }
|
||||||
|
CValueCacheUser user1(&cache);
|
||||||
|
CValueCacheUser user2(&cache);
|
||||||
|
CRegularThread thread;
|
||||||
|
user2.moveToThread(&thread);
|
||||||
|
thread.start();
|
||||||
|
testCommon(user1, user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestValueCache::distributed()
|
||||||
|
{
|
||||||
|
CIdentifier thisProcess;
|
||||||
|
CIdentifier otherProcess;
|
||||||
|
auto json = otherProcess.toJson();
|
||||||
|
json.insert("processId", otherProcess.getProcessId() + 1);
|
||||||
|
otherProcess.convertFromJson(json);
|
||||||
|
|
||||||
|
CValueCache thisCache(CValueCache::Distributed);
|
||||||
|
CValueCache otherCache(CValueCache::Distributed);
|
||||||
|
connect(&thisCache, &CValueCache::valuesChangedByLocal, &thisCache, [ & ](const CVariantMap &values)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(&thisCache, "changeValuesFromRemote", Q_ARG(BlackMisc::CVariantMap, values), Q_ARG(BlackMisc::CIdentifier, thisProcess));
|
||||||
|
QMetaObject::invokeMethod(&otherCache, "changeValuesFromRemote", Q_ARG(BlackMisc::CVariantMap, values), Q_ARG(BlackMisc::CIdentifier, otherProcess));
|
||||||
|
});
|
||||||
|
connect(&otherCache, &CValueCache::valuesChangedByLocal, &thisCache, [ & ](const CVariantMap &values)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(&thisCache, "changeValuesFromRemote", Q_ARG(BlackMisc::CVariantMap, values), Q_ARG(BlackMisc::CIdentifier, otherProcess));
|
||||||
|
QMetaObject::invokeMethod(&otherCache, "changeValuesFromRemote", Q_ARG(BlackMisc::CVariantMap, values), Q_ARG(BlackMisc::CIdentifier, thisProcess));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) { QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Uninitialized value")); }
|
||||||
|
CValueCacheUser thisUser(&thisCache);
|
||||||
|
CValueCacheUser otherUser(&otherCache);
|
||||||
|
|
||||||
|
CRegularThread thread;
|
||||||
|
otherCache.moveToThread(&thread);
|
||||||
|
otherUser.moveToThread(&thread);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
singleShotAndWait(&otherUser, [ & ] { otherUser.m_value1.set(99); });
|
||||||
|
thisUser.m_value1.set(100);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
waitForQueueOf(&otherUser);
|
||||||
|
QVERIFY(thisUser.slotFired() != otherUser.slotFired());
|
||||||
|
auto thisValue = thisUser.m_value1.get();
|
||||||
|
singleShotAndWait(&otherUser, [ & ] { QVERIFY(thisValue == otherUser.m_value1.get()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestValueCache::batched()
|
||||||
|
{
|
||||||
|
CValueCache cache(CValueCache::LocalOnly);
|
||||||
|
for (int i = 0; i < 4; ++i) { QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Uninitialized value")); }
|
||||||
|
CValueCacheUser user1(&cache);
|
||||||
|
CValueCacheUser user2(&cache);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto batch = cache.batchChanges(&user1);
|
||||||
|
user1.m_value1.set(42);
|
||||||
|
user1.m_value2.set(42);
|
||||||
|
}
|
||||||
|
QVERIFY(! user1.slotFired());
|
||||||
|
QVERIFY(user2.slotFired());
|
||||||
|
singleShotAndWait(&user2, [ & ]
|
||||||
|
{
|
||||||
|
QVERIFY(user2.m_value1.get() == 42);
|
||||||
|
QVERIFY(user2.m_value2.get() == 42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestValueCache::json()
|
||||||
|
{
|
||||||
|
QJsonObject testJson
|
||||||
|
{
|
||||||
|
{ "value1", CVariant::from(1).toJson() },
|
||||||
|
{ "value2", CVariant::from(2).toJson() },
|
||||||
|
{ "value3", CVariant::from(3).toJson() }
|
||||||
|
};
|
||||||
|
CVariantMap testData
|
||||||
|
{
|
||||||
|
{ "value1", CVariant::from(1) },
|
||||||
|
{ "value2", CVariant::from(2) },
|
||||||
|
{ "value3", CVariant::from(3) }
|
||||||
|
};
|
||||||
|
|
||||||
|
CValueCache cache(CValueCache::LocalOnly);
|
||||||
|
cache.loadFromJson(testJson);
|
||||||
|
QVERIFY(cache.getAllValues() == testData);
|
||||||
|
QVERIFY(cache.saveToJson() == testJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validator(int value)
|
||||||
|
{
|
||||||
|
return value >= 0 && value <= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
CValueCacheUser::CValueCacheUser(CValueCache *cache) :
|
||||||
|
m_value1(cache, "value1", validator, 0, this, &CValueCacheUser::ps_valueChanged),
|
||||||
|
m_value2(cache, "value2", validator, 0, this, &CValueCacheUser::ps_valueChanged)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void CValueCacheUser::ps_valueChanged()
|
||||||
|
{
|
||||||
|
m_slotFired.set_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CValueCacheUser::slotFired()
|
||||||
|
{
|
||||||
|
auto status = m_slotFired.get_future().wait_for(std::chrono::milliseconds(250));
|
||||||
|
m_slotFired = std::promise<void>();
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case std::future_status::deferred: default: QTEST_ASSERT(false);
|
||||||
|
case std::future_status::ready: return true;
|
||||||
|
case std::future_status::timeout: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
tests/blackmisc/testvaluecache.h
Normal file
77
tests/blackmisc/testvaluecache.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/* Copyright (C) 2015
|
||||||
|
* swift project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
|
||||||
|
* including this file, may be copied, modified, propagated, or distributed except according to the terms
|
||||||
|
* contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! \file
|
||||||
|
|
||||||
|
#ifndef BLACKMISCTEST_TESTVALUECACHE_H
|
||||||
|
#define BLACKMISCTEST_TESTVALUECACHE_H
|
||||||
|
|
||||||
|
#include "blackmisc/valuecache.h"
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
namespace BlackMiscTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Unit tests for value cache system.
|
||||||
|
*/
|
||||||
|
class CTestValueCache : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Constructor.
|
||||||
|
explicit CTestValueCache(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
//! Test insert and getAll functions of CValueCache.
|
||||||
|
void insertAndGet();
|
||||||
|
|
||||||
|
//! Test using CCached locally in one process.
|
||||||
|
void localOnly();
|
||||||
|
|
||||||
|
//! Test using CCached locally in one process, with multiple threads.
|
||||||
|
void localOnlyWithThreads();
|
||||||
|
|
||||||
|
//! Test using CCached distributed among two processes.
|
||||||
|
void distributed();
|
||||||
|
|
||||||
|
//! Test using batched changes.
|
||||||
|
void batched();
|
||||||
|
|
||||||
|
//! Test Json serialization.
|
||||||
|
void json();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Simple class which uses CCached, for testing.
|
||||||
|
*/
|
||||||
|
class CValueCacheUser : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Constructor.
|
||||||
|
explicit CValueCacheUser(BlackMisc::CValueCache *cache);
|
||||||
|
|
||||||
|
//! Slot to be called when a cached value changes.
|
||||||
|
void ps_valueChanged();
|
||||||
|
|
||||||
|
//! Detect whether the slot was called, for verification.
|
||||||
|
bool slotFired();
|
||||||
|
|
||||||
|
std::promise<void> m_slotFired; //!< Flag marking whether the slot was called.
|
||||||
|
BlackMisc::CCached<int> m_value1; //!< First cached value.
|
||||||
|
BlackMisc::CCached<int> m_value2; //!< Second cached value.
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user