diff --git a/provider/include/TileCache.h b/provider/include/TileCache.h new file mode 100644 index 0000000..8400c06 --- /dev/null +++ b/provider/include/TileCache.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * + * Project: AvNav ocharts-provider + * Purpose: Tile Cache + * Author: Andreas Vogel + * + *************************************************************************** + * Copyright (C) 2024 by Andreas Vogel * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + *************************************************************************** + * + */ + +#ifndef _TILECACHE_H +#define _TILECACHE_H +#include "Types.h" +#include "ItemStatus.h" +#include "MD5.h" +#include +#include +#include +#include "Timer.h" +#include "SimpleThread.h" +#include "Tiles.h" + +class TileCache : public ItemStatus{ + public: + using Png=DataPtr; + using Ptr=std::shared_ptr; + class CacheDescription{ + public: + int settingsSequence=0; + String setHash; + int setSequence=0; + bool isNewer(const CacheDescription &other) const{ + if (settingsSequence == other.settingsSequence && + setHash == other.setHash + ) return false; + if (settingsSequence < other.settingsSequence + || setSequence < other.setSequence) return false; + return true; + } + bool equals(const CacheDescription &other) const{ + return (settingsSequence == other.settingsSequence + && setHash == other.setHash); + } + }; + private: + class CacheEntry{ + public: + using Ptr=std::shared_ptr; + CacheDescription description; + Png data; + Timer::SteadyTimePoint lastAccess; + size_t size=2*sizeof(MD5Name)+sizeof(Timer::SteadyTimePoint); + CacheEntry(Png d, const CacheDescription &dsc, size_t keySize): + data(d),description(dsc){ + lastAccess=Timer::steadyNow(); + size+=data->size()+keySize; + size=size/1024; + } + bool isNewer(const CacheEntry &other) const{ + return description.isNewer(other.description); + } + }; + std::mutex lock; + using Data=std::map; + std::atomic numEntries={0}; + std::atomic numKb={0}; + size_t maxMem; + Data cache; + String getKey(const TileInfo &tile); + public: + bool stopAudit=false; + TileCache(size_t max); + virtual void ToJson(StatusStream &stream); + void cleanup(); + void clean(String setKey=""); + void cleanBySettings(int remainingSeqeunce); + bool addTile(Png d, const CacheDescription &description, const TileInfo &tile); + Png getTile(const CacheDescription &description, const TileInfo &tile); + void stop(){stopAudit=true;} + +}; +#endif \ No newline at end of file diff --git a/provider/src/TileCache.cpp b/provider/src/TileCache.cpp new file mode 100644 index 0000000..fdeec5a --- /dev/null +++ b/provider/src/TileCache.cpp @@ -0,0 +1,164 @@ +/****************************************************************************** + * + * Project: AvNav ocharts-provider + * Purpose: Tile Cache + * Author: Andreas Vogel + * + *************************************************************************** + * Copyright (C) 2024 by Andreas Vogel * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + *************************************************************************** + * + */ + +#include "TileCache.h" +#include "StringHelper.h" +String TileCache::getKey(const TileInfo &tile){ + return FMT("%s/%d/%d/%d",tile.chartSetKey,tile.zoom,tile.x,tile.y); +} +void TileCache::ToJson(StatusStream &stream){ + stream["numEntries"]=(int)numEntries; + stream["numKb"]=(int)numKb; +} +class CHelper{ + public: + String key; + Timer::SteadyTimePoint lastAccess; + size_t kb=0; + CHelper(const String &k, const Timer::SteadyTimePoint &l, size_t s): + key(k),lastAccess(l),kb(s){} +}; +void TileCache::cleanup(){ + if (numKb <= maxMem) return; + using CHList=std::vector; + std::unique_ptr items=std::make_unique(); + { + Synchronized l(lock); + items->reserve(cache.size()); + for (auto &&[key,ci]:cache){ + items->push_back(CHelper(key,ci->lastAccess, ci->size)); + } + std::sort(items->begin(),items->end(),[](CHelper const & c1, CHelper const & c2){ + return c1.lastAccess < c2.lastAccess; + }); + auto last=items->begin(); + int removeKb=0; + for (auto it=items->begin();it != items->end();it++){ + removeKb+=it->kb; + if (removeKb >= (numKb-maxMem)){ + last=it; + break; + } + } + for(auto it=items->begin();it!= last && it != items->end();it++){ + cache.erase(it->key); + } + int newKb=numKb-removeKb; + if (newKb < 0) newKb=0; + numKb=newKb; + numEntries=cache.size(); + } +} +void TileCache::clean(String setKey){ + Synchronized l(lock); + size_t current=cache.size(); + int currentKb=numKb; + int newKb=0; + if (setKey.empty()){ + cache.clear(); + numEntries=0; + LOG_INFO("deleted %d entries from tile cache, freeing ~ %dkb",current,(int)numKb); + numKb=0; + return; + } + avnav::erase_if(cache,[setKey,&newKb](Data::reference &item){ + bool rt=StringHelper::startsWith(item.first,setKey); + if (!rt){ + int kb=item.second->size; + newKb+=kb; + } + return rt; + }); + numKb=newKb; + if (cache.size() != current){ + LOG_INFO("clean: deleted %d entries from tile cache, freeing ~ %dkb",current,(currentKb-newKb)); + } + numEntries=cache.size(); +} +void TileCache::cleanBySettings(int remainingSequence){ + Synchronized l(lock); + size_t current=cache.size(); + int currentKb=numKb; + int newKb=0; + avnav::erase_if(cache,[remainingSequence,&newKb](Data::reference &item){ + bool rt=item.second->description.settingsSequence != remainingSequence; + if (!rt){ + int kb=item.second->size; + newKb+=kb; + } + return rt; + }); + numKb=newKb; + if (cache.size() != current){ + LOG_INFO("cleanBySettings: deleted %d entries from tile cache, freeing ~ %dkb",current,(currentKb-newKb)); + } + numEntries=cache.size(); +} +bool TileCache::addTile(TileCache::Png d, const TileCache::CacheDescription &description, const TileInfo &tile){ + Synchronized l(lock); + String key=getKey(tile); + auto cur=cache.find(key); + bool rt=false; + if (cur == cache.end()){ + CacheEntry::Ptr ne=std::make_shared(d,description,key.size()); + cache[key]=ne; + numKb+=ne->size; + rt=true; + } + else if (description.isNewer(cur->second->description)){ + int oldKb=cur->second->size/1024; + CacheEntry::Ptr ne=std::make_shared(d,description,key.size()); + cache[key]=ne; + int newKb=ne->size; + numKb+=(newKb-oldKb); + rt=true; + } + numEntries=cache.size(); + return rt; +} +TileCache::Png TileCache::getTile(const TileCache::CacheDescription &description, const TileInfo &tile){ + Synchronized l(lock); + String key=getKey(tile); + auto cur=cache.find(key); + if (cur == cache.end()){ + return TileCache::Png(); + } + if (description.equals(cur->second->description)){ + cur->second->lastAccess=Timer::steadyNow(); + return cur->second->data; + } + return TileCache::Png(); +} + +TileCache::TileCache(size_t max):maxMem(max){ + std::thread([this](){ + while (! this->stopAudit){ + Timer::microSleep(1000000L); + this->cleanup(); + } + }).detach(); +}