Skip to content

Commit

Permalink
Merge pull request #1631 from Karry/visual-cache-flush
Browse files Browse the repository at this point in the history
ability to cleanup visual cache
  • Loading branch information
Framstag authored Dec 8, 2024
2 parents 9550c19 + 6883a52 commit c0b9c29
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public slots:
virtual void Initialize() = 0;

virtual void InvalidateVisualCache() = 0;
virtual void FlushVisualCaches(const std::chrono::milliseconds &idleMs) = 0;
virtual void onStylesheetFilenameChanged();
virtual void onDatabaseLoaded(osmscout::GeoBox boundingBox) = 0;

Expand Down
7 changes: 7 additions & 0 deletions libosmscout-client-qt/include/osmscoutclientqt/MapWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ public slots:
}
};

Slot<std::chrono::milliseconds> flushCachesSlot {
std::bind(&MapWidget::FlushCaches, this, std::placeholders::_1)
};


private slots:

virtual void onTap(const QPoint p);
Expand Down Expand Up @@ -535,6 +540,8 @@ private slots:
preventMouseStealing = b;
}

void FlushCaches(const std::chrono::milliseconds &idleMs);

/**
* Helper for loading SVG graphics
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class OSMSCOUT_CLIENT_QT_API PlaneMapRenderer : public MapRenderer {
// reverse order is possible
QImage *finishedImage;
size_t finishedEpoch{0};
std::chrono::steady_clock::time_point
finishedLastUsage;
osmscout::GeoCoord finishedCoord;
double finishedAngle;
osmscout::Magnification finishedMagnification;
Expand All @@ -83,6 +85,7 @@ class OSMSCOUT_CLIENT_QT_API PlaneMapRenderer : public MapRenderer {
public slots:
virtual void Initialize();
virtual void InvalidateVisualCache();
virtual void FlushVisualCaches(const std::chrono::milliseconds &idleMs);
virtual void onDatabaseLoaded(osmscout::GeoBox boundingBox);

void DrawMap();
Expand Down
11 changes: 6 additions & 5 deletions libosmscout-client-qt/include/osmscoutclientqt/TileCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
#include <osmscout/util/GeoBox.h>
#include <osmscoutclientqt/ClientQtImportExport.h>

//#define DEBUG_TILE_CACHE
#include <chrono>

// #define DEBUG_TILE_CACHE

namespace osmscout {

Expand Down Expand Up @@ -69,7 +71,7 @@ QDebug& operator<<(QDebug &out, const TileCacheKey &key);
*/
struct TileCacheVal
{
QElapsedTimer lastAccess;
std::chrono::steady_clock::time_point lastAccess;
QImage image;
size_t epoch;
};
Expand Down Expand Up @@ -97,7 +99,7 @@ class OSMSCOUT_CLIENT_QT_API TileCache : public QObject

public:
explicit TileCache(size_t cacheSize);
~TileCache() override;
~TileCache() override = default;

/**
* remove all pending requests
Expand Down Expand Up @@ -143,7 +145,7 @@ class OSMSCOUT_CLIENT_QT_API TileCache : public QObject
bool removeRequest(uint32_t zoomLevel, uint32_t x, uint32_t y);
void put(uint32_t zoomLevel, uint32_t x, uint32_t y, const QImage &image, size_t epoch = 0);

void cleanupCache();
void cleanupCache(uint32_t maxRemove, const std::chrono::milliseconds &maximumLifetime);

inline size_t getEpoch() const
{
Expand All @@ -159,7 +161,6 @@ class OSMSCOUT_CLIENT_QT_API TileCache : public QObject
QHash<TileCacheKey, TileCacheVal> tiles;
QHash<TileCacheKey, RequestState> requests;
size_t cacheSize; // maximum count of elements in cache
uint32_t maximumLivetimeMs;
size_t epoch{0};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ class OSMSCOUT_CLIENT_QT_API TiledMapOverlay : public MapOverlay
bool enabled;
QColor transparentColor;


Slot<std::chrono::milliseconds> flushCachesSlot {
std::bind(&TiledMapOverlay::FlushCaches, this, std::placeholders::_1)
};

public slots:
void tileDownloaded(uint32_t zoomLevel, uint32_t x, uint32_t y);

Expand All @@ -102,6 +107,8 @@ public slots:

bool isEnabled();
void setEnabled(bool b);

void FlushCaches(const std::chrono::milliseconds &idleMs);
};

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class OSMSCOUT_CLIENT_QT_API TiledMapRenderer : public MapRenderer {
public slots:
virtual void Initialize();
virtual void InvalidateVisualCache();
virtual void FlushVisualCaches(const std::chrono::milliseconds &idleMs);
virtual void onStylesheetFilenameChanged();
virtual void onDatabaseLoaded(osmscout::GeoBox boundingBox);

Expand Down
6 changes: 6 additions & 0 deletions libosmscout-client-qt/src/osmscoutclientqt/MapWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ MapWidget::MapWidget(QQuickItem* parent)
dbThread->stylesheetFilenameChanged.Connect(stylesheetFilenameChangedSlot);
dbThread->styleErrorsChanged.Connect(styleErrorsChangedSlot);
dbThread->databaseLoadFinished.Connect(databaseLoadedSlot);
dbThread->flushCachesSignal.Connect(flushCachesSlot);

tapRecognizer.setPhysicalDpi(dbThread->GetPhysicalDpi());

Expand Down Expand Up @@ -1066,4 +1067,9 @@ void MapWidget::SetRenderingType(QString strType)
emit renderingTypeChanged(GetRenderingType());
}
}

void MapWidget::FlushCaches(const std::chrono::milliseconds &idleMs)
{
renderer->FlushVisualCaches(idleMs);
}
}
12 changes: 12 additions & 0 deletions libosmscout-client-qt/src/osmscoutclientqt/PlaneMapRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ void PlaneMapRenderer::InvalidateVisualCache()
emit Redraw();
}

void PlaneMapRenderer::FlushVisualCaches(const std::chrono::milliseconds &idleMs)
{
{
QMutexLocker finishedLocker(&finishedMutex);
if (std::chrono::steady_clock::now() - finishedLastUsage > idleMs) {
osmscout::log.Debug() << "Flush finished image";
finishedImage = nullptr;
}
}
}

/**
* Render map defined by request to painter
* @param painter
Expand Down Expand Up @@ -230,6 +241,7 @@ bool PlaneMapRenderer::RenderMap(QPainter& painter,
targetRectangle.setSize(sourceRectangle.size());
}

finishedLastUsage=std::chrono::steady_clock::now();
painter.drawImage(targetRectangle,
*finishedImage,
sourceRectangle);
Expand Down
95 changes: 44 additions & 51 deletions libosmscout-client-qt/src/osmscoutclientqt/TileCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,7 @@ QDebug& operator<<(QDebug &out, const TileCacheKey &key)
}

TileCache::TileCache(size_t cacheSize):
tiles(),
requests(),
cacheSize(cacheSize),
maximumLivetimeMs(5 * 60 * 1000)
{
}

TileCache::~TileCache()
cacheSize(cacheSize)
{
}

Expand Down Expand Up @@ -174,10 +167,10 @@ TileCacheVal TileCache::get(uint32_t zoomLevel, uint32_t x, uint32_t y)
TileCacheKey key = {zoomLevel, x, y};
if (!tiles.contains(key)){
qWarning() << this << "No tile in cache for key " << key;
return {QElapsedTimer(), QImage(), epoch}; // throw std::underflow_error ?
return {std::chrono::steady_clock::now(), QImage(), epoch}; // throw std::underflow_error ?
}
TileCacheVal val = tiles.value(key);
val.lastAccess.start();
val.lastAccess = std::chrono::steady_clock::now();
tiles.insert(key, val);
return val;
}
Expand All @@ -197,70 +190,70 @@ void TileCache::put(uint32_t zoomLevel, uint32_t x, uint32_t y, const QImage &im
{
removeRequest(zoomLevel, x, y);
TileCacheKey key = {zoomLevel, x, y};
QElapsedTimer now;
now.start();
TileCacheVal val = {now, image, epoch};
TileCacheVal val = {std::chrono::steady_clock::now(), image, epoch};

#ifdef DEBUG_TILE_CACHE
qDebug() << this << "inserting tile" << key;
#endif

tiles.insert(key, val);

cleanupCache();
if (tiles.size() > (int)cacheSize) {
cleanupCache(cacheSize / 10, std::chrono::minutes{5});
}
}

void TileCache::cleanupCache()
void TileCache::cleanupCache(uint32_t maxRemove, const std::chrono::milliseconds &maximumLifetime)
{

if (tiles.size() > (int)cacheSize){
/**
* maximum size reached
*
* first, we will iterate over all entries and remove up to 10% tiles
* older than `maximumLivetimeMs`, if no such entry found, remove oldest
*
* Goal is to remove more items at once and minimise frequency of this expensive cleaning
*/
if (maxRemove==std::numeric_limits<uint32_t>::max() && maximumLifetime.count() == 0) {
// flush cache completely
tiles.clear();
return;
}

/**
* first, we will iterate over all entries and remove up to maxRemove tiles
* older than `maximumLifetime`, if no such entry found and size is bigger than cacheSize,
* remove oldest tile
*
* Goal is to remove more items at once and minimise frequency of this expensive cleaning
*/

#ifdef DEBUG_TILE_CACHE
qDebug() << this << "Cleaning tile cache (" << cacheSize << ")";
qDebug() << this << "Cleaning tile cache (" << cacheSize << ")";
#endif

uint32_t removed = 0;
int oldest = 0;
TileCacheKey key;
TileCacheKey oldestKey;

QMutableHashIterator<TileCacheKey, TileCacheVal> it(tiles);
while (it.hasNext() && removed < (cacheSize / 10)){
it.next();
uint32_t removed = 0;
std::chrono::steady_clock::duration oldest;
TileCacheKey key;
TileCacheKey oldestKey;
auto now = std::chrono::steady_clock::now();

//QHash<TileCacheKey, TileCacheVal>::const_iterator it = tiles.constBegin();
//while (it != tiles.constEnd() && removed < (cacheSize / 10)){
QMutableHashIterator<TileCacheKey, TileCacheVal> it(tiles);
while (it.hasNext() && removed < maxRemove){
it.next();

key = it.key();
TileCacheVal val = it.value();
key = it.key();
TileCacheVal val = it.value();

int elapsed = val.lastAccess.elapsed();
if (elapsed > oldest){
oldest = elapsed;
oldestKey = key;
}
auto elapsed = now - val.lastAccess;
if (elapsed > oldest){
oldest = elapsed;
oldestKey = key;
}

if (elapsed > (int)maximumLivetimeMs){
if (elapsed > duration_cast<std::chrono::steady_clock::duration>(maximumLifetime)){
#ifdef DEBUG_TILE_CACHE
qDebug() << this << "removing" << key;
qDebug() << this << "removing" << key;
#endif

//tiles.remove(key);
it.remove();
it.remove();

removed ++;
}
//++it;
removed ++;
}
if (removed == 0 && oldest > 0){
}
if (tiles.size() > (int)cacheSize){
if (removed == 0 && oldest.count() > 0){
key = oldestKey;
#ifdef DEBUG_TILE_CACHE
qDebug() << this << "removing" << key;
Expand Down
14 changes: 12 additions & 2 deletions libosmscout-client-qt/src/osmscoutclientqt/TiledMapOverlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void TileLoaderThread::onProviderChanged(const OnlineTileProvider &newProvider)
{
QMutexLocker locker(&tileCacheMutex);
onlineTileCache.clearPendingRequests();
onlineTileCache.cleanupCache();
onlineTileCache.invalidate();

provider=newProvider;
if (tileDownloader!=nullptr){
Expand Down Expand Up @@ -150,6 +150,9 @@ TiledMapOverlay::TiledMapOverlay(QQuickItem* parent):
connect(loader, &TileLoaderThread::downloaded,
this, &TiledMapOverlay::tileDownloaded,
Qt::QueuedConnection);

auto dbThread=OSMScoutQt::GetInstance().GetDBThread();
dbThread->flushCachesSignal.Connect(flushCachesSlot);
}

TiledMapOverlay::~TiledMapOverlay()
Expand Down Expand Up @@ -239,9 +242,16 @@ void TiledMapOverlay::setEnabled(bool b)
if (!enabled){
// cleanup cache to release memory
loader->accessCache([&](TileCache& onlineTileCache) {
onlineTileCache.cleanupCache();
onlineTileCache.cleanupCache(std::numeric_limits<uint32_t>::max(), std::chrono::milliseconds(0));
});
}
redraw();
}

void TiledMapOverlay::FlushCaches(const std::chrono::milliseconds &idleMs)
{
loader->accessCache([&](TileCache& onlineTileCache) {
onlineTileCache.cleanupCache(std::numeric_limits<uint32_t>::max(), idleMs);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ void TiledMapRenderer::InvalidateVisualCache()
emit Redraw();
}

void TiledMapRenderer::FlushVisualCaches(const std::chrono::milliseconds &idleMs)
{
{
QMutexLocker locker(&tileCacheMutex);
offlineTileCache.cleanupCache(std::numeric_limits<uint32_t>::max(), idleMs);
onlineTileCache.cleanupCache(std::numeric_limits<uint32_t>::max(), idleMs);
}
}

/**
* Render map defined by request to painter
* @param painter
Expand Down
2 changes: 2 additions & 0 deletions libosmscout-client/include/osmscoutclient/DBThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ class OSMSCOUT_CLIENT_API DBThread: public AsyncWorker
std::bind(&DBThread::FlushCaches, this, std::placeholders::_1)
};

Signal<std::chrono::milliseconds> flushCachesSignal;

private:
MapManagerRef mapManager;
std::string basemapLookupDirectory;
Expand Down
2 changes: 2 additions & 0 deletions libosmscout-client/src/osmscoutclient/DBThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ const std::map<std::string,bool> DBThread::GetStyleFlags() const

CancelableFuture<bool> DBThread::FlushCaches(const std::chrono::milliseconds &idleMs)
{
flushCachesSignal.Emit(idleMs);

return Async<bool>([this, idleMs](const Breaker& breaker) {
if (breaker.IsAborted()) {
return false;
Expand Down

0 comments on commit c0b9c29

Please sign in to comment.