refs #601 Simplify syncLoad and rename to synchronize.

Now it doesn't return anything, it just causes the next async get() to be synchronized with the latest loaded value.
It does this by hooking into the queue introduced in the previous commit.
This commit is contained in:
Mathew Sutcliffe
2016-03-10 00:09:14 +00:00
parent 06ad77ee9d
commit c6a038aaa8
4 changed files with 64 additions and 38 deletions

View File

@@ -92,19 +92,15 @@ namespace BlackMisc
return enumerateFiles(persistentStore());
}
std::future<CVariant> CDataCache::syncLoad(QObject *pageOwner, const QString &key)
bool CDataCache::synchronize(const QString &key)
{
auto future = m_revision.promiseLoadedValue(pageOwner, key, getTimestampSync(key));
auto future = m_revision.promiseLoadedValue(key, getTimestampSync(key));
if (future.valid())
{
return future;
}
else // value is not awaiting load, so immediately return the current value
{
std::promise<CVariant> p;
p.set_value(getValueSync(key));
return p.get_future();
future.wait();
return true;
}
return false;
}
void CDataCache::setTimeToLive(const QString &key, int ttl)
@@ -186,6 +182,25 @@ namespace BlackMisc
}
}
void CDataPageQueue::setQueuedValueFromCache(const QString &key)
{
QMutexLocker lock(&m_mutex);
decltype(m_queue) filtered;
for (auto &pair : m_queue)
{
if (pair.first.contains(key))
{
filtered.push_back({ pair.first.takeByKey(key), pair.second });
}
}
lock.unlock();
for (const auto &pair : filtered)
{
m_page->setValuesFromCache(pair.first, pair.second);
}
}
CDataCacheSerializer::CDataCacheSerializer(CDataCache *owner, const QString &revisionFileName) :
CContinuousWorker(owner),
m_cache(owner),
@@ -236,19 +251,14 @@ namespace BlackMisc
}
}
void CDataCacheSerializer::deliverPromises(std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> i_promises)
void CDataCacheSerializer::deliverPromises(std::vector<std::promise<void>> i_promises)
{
auto changes = m_deferredChanges;
auto promises = std::make_shared<decltype(i_promises)>(std::move(i_promises)); // \todo use C++14 lambda init-capture
QTimer::singleShot(0, Qt::PreciseTimer, this, [this, changes, promises]
QTimer::singleShot(0, Qt::PreciseTimer, this, [this, promises]
{
for (auto &tuple : *promises)
for (auto &promise : *promises)
{
QString key;
std::promise<CVariant> promise;
std::tie(std::ignore, key, promise) = std::move(tuple);
promise.set_value(changes.value(key).first);
promise.set_value();
}
});
}
@@ -391,21 +401,21 @@ namespace BlackMisc
return (m_updateInProgress || beginUpdate({{ key, timestamp }}, false)) && m_timestamps.contains(key);
}
std::future<CVariant> CDataCacheRevision::promiseLoadedValue(QObject *pageOwner, const QString &key, qint64 currentTimestamp)
std::future<void> CDataCacheRevision::promiseLoadedValue(const QString &key, qint64 currentTimestamp)
{
QMutexLocker lock(&m_mutex);
if (isNewerValueAvailable(key, currentTimestamp))
{
std::promise<CVariant> promise;
std::promise<void> promise;
auto future = promise.get_future();
m_promises.emplace_back(pageOwner, key, std::move(promise));
m_promises.push_back(std::move(promise));
return future;
}
return {};
}
std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> CDataCacheRevision::loadedValuePromises()
std::vector<std::promise<void>> CDataCacheRevision::loadedValuePromises()
{
QMutexLocker lock(&m_mutex);

View File

@@ -43,6 +43,9 @@ namespace BlackMisc
//! Synchronize with changes queued by queueValuesFromCache, if the mutex is not currently locked.
void trySetQueuedValuesFromCache();
//! Synchronize with one specific change in the queue, leave the rest for later.
void setQueuedValueFromCache(const QString &key);
private:
CValuePage *m_page = nullptr;
QList<std::pair<CValueCachePacket, QObject*>> m_queue;
@@ -96,10 +99,10 @@ namespace BlackMisc
bool isNewerValueAvailable(const QString &key, qint64 timestamp);
//! Return a future which will be made ready when the value is loaded. Future is invalid if value is not loading.
std::future<CVariant> promiseLoadedValue(QObject *pageOwner, const QString &key, qint64 currentTimestamp);
std::future<void> promiseLoadedValue(const QString &key, qint64 currentTimestamp);
//! Returns (by move) the container of promises to load values.
std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> loadedValuePromises();
std::vector<std::promise<void>> loadedValuePromises();
//! Set TTL value that will be written to the revision file.
void setTimeToLive(const QString &key, int ttl);
@@ -117,7 +120,7 @@ namespace BlackMisc
QUuid m_uuid;
QMap<QString, qint64> m_timestamps;
QMap<QString, qint64> m_timesToLive;
std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> m_promises;
std::vector<std::promise<void>> m_promises;
static QJsonObject toJson(const QMap<QString, qint64> &timestamps);
static QMap<QString, qint64> fromJson(const QJsonObject &timestamps);
@@ -152,7 +155,7 @@ namespace BlackMisc
private:
const QString &persistentStore() const;
void applyDeferredChanges();
void deliverPromises(std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>>);
void deliverPromises(std::vector<std::promise<void>>);
CDataCache *const m_cache = nullptr;
QUuid m_revision;
@@ -182,8 +185,8 @@ namespace BlackMisc
//! Return all files where data may be stored.
QStringList enumerateStore() const;
//! Method used for implementing CData::syncLoad.
std::future<CVariant> syncLoad(QObject *pageOwner, const QString &key);
//! Method used for implementing CData::synchronize.
bool synchronize(const QString &key);
//! Method used for implementing TTL.
void setTimeToLive(const QString &key, int ttl);
@@ -225,8 +228,7 @@ namespace BlackMisc
//! 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::isValid, Trait::defaultValue(), owner, slot),
m_owner(owner)
CData::CCached(CDataCache::instance(), Trait::key(), Trait::isValid, Trait::defaultValue(), owner, slot)
{
if (Trait::timeToLive() >= 0) { CDataCache::instance()->setTimeToLive(Trait::key(), Trait::timeToLive()); }
}
@@ -243,15 +245,17 @@ namespace BlackMisc
//! Don't change the value, but write a new timestamp, to extend the life of the value.
void renewTimestamp(qint64 timestamp) { return CDataCache::instance()->renewTimestamp(this->getKey(), timestamp); }
//! Return a future providing the value. If the value is still loading, the future will wait for it.
//! If the value is not present, the variant is null. Bypasses async get and inhibits notification slot.
std::future<CVariant> syncLoad() { return CDataCache::instance()->syncLoad(m_owner, this->getKey()); }
//! If the value is currently being loaded, wait for it to finish loading, and call the notification slot, if any.
void synchronize()
{
auto *queue = this->m_page.template findChild<Private::CDataPageQueue *>();
Q_ASSERT(queue);
CDataCache::instance()->synchronize(this->getKey());
queue->setQueuedValueFromCache(this->getKey());
}
//! Data cache doesn't support setAndSave (because set() already causes save anyway).
CStatusMessage setAndSave(const typename Trait::type &value, qint64 timestamp = 0) = delete;
private:
QObject *m_owner = nullptr;
};
/*!

View File

@@ -74,6 +74,14 @@ namespace BlackMisc
return result;
}
CValueCachePacket CValueCachePacket::takeByKey(const QString &key)
{
auto copy = *this;
remove(key);
copy.removeByKeyIf([ = ](const QString &key2) { return key2 != key; });
return copy;
}
void CValueCachePacket::registerMetadata()
{
MetaType::registerMetadata();

View File

@@ -60,6 +60,9 @@ namespace BlackMisc
//! Discard values and return as map of timestamps.
QMap<QString, qint64> toTimestampMap() const;
//! Remove value matching the given key, and return it in a separate packet.
CValueCachePacket takeByKey(const QString &key);
//! \copydoc BlackMisc::Mixin::MetaType::registerMetadata
static void registerMetadata();
@@ -330,8 +333,9 @@ namespace BlackMisc
QVariant getVariantCopy() const { return m_page.getValueCopy(m_element).getQVariant(); }
bool isValid() const { return m_page.isValid(m_element, qMetaTypeId<T>()); }
Private::CValuePage &m_page;
Private::CValuePage::Element &m_element;
protected:
Private::CValuePage &m_page; //!< \private
Private::CValuePage::Element &m_element; //!< \private
};
/*!