HaikuDepot: LRU Cache for Icons

Only keep a fixed number of icons in memory at once.

Completes To #15370

Change-Id: I23e3a4fa7559894034f45afb3b536910ea037078
Reviewed-on: https://review.haiku-os.org/c/haiku/+/3367
Reviewed-by: Rene Gollent <[email protected]>
This commit is contained in:
Andrew Lindesay
2020-11-15 20:16:14 +00:00
committed by Adrien Destugues
parent 55d6d58d9f
commit 027d608682
8 changed files with 382 additions and 3 deletions
+208
View File
@@ -0,0 +1,208 @@
/*
* Copyright 2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef LRU_CACHE_H
#define LRU_CACHE_H
#include <HashMap.h>
namespace BPrivate {
template<typename Key, typename Value>
class LRUOrderingNode {
private:
typedef LRUOrderingNode<Key, Value> LRUNode;
public:
LRUOrderingNode()
:
fKey(),
fValue(),
fOlder(NULL),
fNewer(NULL)
{
}
LRUOrderingNode(const Key& key, const Value& value)
:
fKey(key),
fValue(value),
fOlder(NULL),
fNewer(NULL)
{
}
Key fKey;
Value fValue;
LRUNode* fOlder;
LRUNode* fNewer;
};
/*! \brief This is a hash map that maintains a limited number of entries. Once
this number of entries has been exceeded then it will start to discard
entries. The first entries to be discarded are the ones that are the least
recently used; hence the prefix "LRU".
*/
template<typename Key, typename Value>
class LRUCache {
public:
typedef LRUOrderingNode<Key, Value> LRUNode;
LRUCache(int32 limit)
:
fNewestNode(NULL),
fOldestNode(NULL),
fLimit(limit)
{
if (fLimit < 0)
fLimit = 0;
}
~LRUCache()
{
Clear();
}
status_t InitCheck() const
{
return fMap.InitCheck();
}
status_t Put(const Key& key, const Value& value)
{
LRUNode* node = fMap.Get(key);
if (node != NULL) {
if (node->fValue != value) {
node->fValue = value;
_DisconnectNodeAndMakeNewest(node);
}
} else {
node = new(std::nothrow) LRUNode(key, value);
if (node == NULL)
return B_NO_MEMORY;
status_t result = fMap.Put(key, node);
if (result != B_OK)
return result;
_SetNewestNode(node);
_PurgeExcess();
}
return B_OK;
}
Value Remove(const Key& key)
{
LRUNode* node = fMap.Get(key);
if (node != NULL) {
_DisconnectNode(node);
Value result = node->fValue;
fMap.Remove(key);
delete node;
return result;
}
return Value();
}
void Clear()
{
fMap.Clear();
LRUNode* node = fNewestNode;
while (node != NULL) {
LRUNode *next = node->fOlder;
delete node;
node = next;
}
}
Value Get(const Key& key)
{
LRUNode* node = fMap.Get(key);
if (node != NULL) {
_DisconnectNodeAndMakeNewest(node);
return node->fValue;
}
return Value();
}
bool ContainsKey(const Key& key) const
{
return fMap.ContainsKey(key);
}
int32 Size() const
{
return fMap.Size();
}
private:
void _DisconnectNodeAndMakeNewest(LRUNode* node) {
if (node != fNewestNode) {
_DisconnectNode(node);
node->fOlder = NULL;
node->fNewer = NULL;
_SetNewestNode(node);
}
}
void _DisconnectNode(LRUNode* node)
{
LRUNode *older = node->fOlder;
LRUNode *newer = node->fNewer;
if (newer != NULL)
newer->fOlder = older;
if (older != NULL)
older->fNewer = newer;
if (fNewestNode == node)
fNewestNode = older;
if (fOldestNode == node)
fOldestNode = newer;
}
void _SetNewestNode(LRUNode* node)
{
if (node != fNewestNode) {
node->fOlder = fNewestNode;
node->fNewer = NULL;
if (fNewestNode != NULL)
fNewestNode->fNewer = node;
fNewestNode = node;
if (fOldestNode == NULL)
fOldestNode = node;
}
}
void _PurgeOldestNode()
{
if (fOldestNode == NULL)
debugger("attempt to purge oldest node but there is none to purge");
Remove(fOldestNode->fKey);
}
void _PurgeExcess()
{
while(Size() > fLimit)
_PurgeOldestNode();
}
protected:
HashMap<Key, LRUNode*> fMap;
LRUNode* fNewestNode;
LRUNode* fOldestNode;
private:
int32 fLimit;
};
}; // namespace BPrivate
using BPrivate::LRUCache;
#endif // LRU_CACHE_H
@@ -2,6 +2,8 @@
* Copyright 2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "PackageIconTarRepository.h"
#include <Autolock.h>
@@ -13,6 +15,9 @@
#include "TarArchiveService.h"
#define LIMIT_ICON_CACHE 50
BitmapRef
PackageIconTarRepository::sDefaultIcon(new(std::nothrow) SharedBitmap(
"application/x-vnd.haiku-package"), true);
@@ -123,7 +128,8 @@ IconTarPtrEntryListener::_LeafNameToBitmapSize(BString& leafName,
PackageIconTarRepository::PackageIconTarRepository()
:
fTarIo(NULL)
fTarIo(NULL),
fIconCache(LIMIT_ICON_CACHE)
{
}
@@ -5,6 +5,7 @@
#ifndef PACKAGE_ICON_TAR_REPOSITORY_H
#define PACKAGE_ICON_TAR_REPOSITORY_H
#include <DataIO.h>
#include <HashMap.h>
#include <HashString.h>
@@ -12,8 +13,9 @@
#include <Path.h>
#include <Referenceable.h>
#include "PackageIconRepository.h"
#include "IconTarPtr.h"
#include "LRUCache.h"
#include "PackageIconRepository.h"
typedef BReference<IconTarPtr> IconTarPtrRef;
@@ -54,7 +56,7 @@ private:
private:
BLocker fLock;
BPositionIO* fTarIo;
HashMap<HashString, BitmapRef>
LRUCache<HashString, BitmapRef>
fIconCache;
HashMap<HashString, IconTarPtrRef>
fIconTarPtrs;
@@ -3,6 +3,7 @@
* Distributed under the terms of the MIT License.
*/
#include <TestSuite.h>
#include <TestSuiteAddon.h>
@@ -14,6 +15,7 @@
#include "TarArchiveServiceTest.h"
#include "ListTest.h"
BTestSuite*
getTestSuite()
{
+1
View File
@@ -15,6 +15,7 @@ UnitTestLib libsharedtest.so :
JsonToMessageTest.cpp
GeolocationTest.cpp
KeymapTest.cpp
LRUCacheTest.cpp
NaturalCompareTest.cpp
: be shared bnetapi [ TargetLibstdc++ ] [ TargetLibsupc++ ]
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright 2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "LRUCacheTest.h"
#include <stdio.h>
#include <HashString.h>
#include <String.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include "LRUCache.h"
LRUCacheTest::LRUCacheTest()
{
}
LRUCacheTest::~LRUCacheTest()
{
}
/*! This tests the insertion of various letters into the map and the subsequent
search for those values later using a binary search.
*/
void
LRUCacheTest::TestAddWithOverflow()
{
LRUCache<HashString, BString> map(5);
BString tmpKey;
BString tmpValue;
// ----------------------
for(char c = 'a'; c <= 'z'; c++) {
tmpKey.SetToFormat("%c", c);
tmpValue.SetToFormat("%c%c", c, c);
map.Put(HashString(tmpKey), tmpValue);
}
// ----------------------
CPPUNIT_ASSERT_EQUAL(5, map.Size());
// oldest entries have been removed.
CPPUNIT_ASSERT_EQUAL(BString(""), map.Get(HashString("a")));
CPPUNIT_ASSERT_EQUAL(BString(""), map.Get(HashString("u")));
// latter entries have been removed.
CPPUNIT_ASSERT_EQUAL(BString("zz"), map.Get(HashString("z")));
CPPUNIT_ASSERT_EQUAL(BString("yy"), map.Get(HashString("y")));
CPPUNIT_ASSERT_EQUAL(BString("xx"), map.Get(HashString("x")));
CPPUNIT_ASSERT_EQUAL(BString("ww"), map.Get(HashString("w")));
CPPUNIT_ASSERT_EQUAL(BString("vv"), map.Get(HashString("v")));
}
/*! This tests the insertion of various letters into the list, but during the
inserts, there are some get operations which will effect which are
considered to be the oldest entries.
*/
void
LRUCacheTest::TestAddWithOverflowWithGets()
{
LRUCache<HashString, BString> map(3);
// ----------------------
map.Put(HashString("Red"), "Rot");
map.Put(HashString("Yellow"), "Gelb");
map.Get(HashString("Red"));
map.Put(HashString("Green"), "Gruen");
map.Put(HashString("Purple"), "Lila");
// ----------------------
CPPUNIT_ASSERT_EQUAL(3, map.Size());
CPPUNIT_ASSERT_EQUAL(BString(""), map.Get(HashString("Yellow")));
CPPUNIT_ASSERT_EQUAL(BString("Rot"), map.Get(HashString("Red")));
CPPUNIT_ASSERT_EQUAL(BString("Gruen"), map.Get(HashString("Green")));
CPPUNIT_ASSERT_EQUAL(BString("Lila"), map.Get(HashString("Purple")));
}
void
LRUCacheTest::TestRemove()
{
LRUCache<HashString, BString> map(3);
// control value
map.Put(HashString("Town"), "Tirau");
map.Put(HashString("Lake"), "Taupo");
// ----------------------
BString resultOcean = map.Remove(HashString("Ocean"));
BString resultLake = map.Remove(HashString("Lake"));
// ----------------------
CPPUNIT_ASSERT_EQUAL(1, map.Size());
CPPUNIT_ASSERT_EQUAL(BString(""), resultOcean);
CPPUNIT_ASSERT_EQUAL(BString("Taupo"), resultLake);
CPPUNIT_ASSERT_EQUAL(BString("Tirau"), map.Get(HashString("Town")));
CPPUNIT_ASSERT_EQUAL(BString(""), map.Get(HashString("Lake")));
CPPUNIT_ASSERT_EQUAL(BString(""), map.Get(HashString("Ocean")));
}
/*static*/ void
LRUCacheTest::AddTests(BTestSuite& parent)
{
CppUnit::TestSuite& suite = *new CppUnit::TestSuite(
"LRUCacheTest");
suite.addTest(
new CppUnit::TestCaller<LRUCacheTest>(
"LRUCacheTest::TestAddWithOverflow",
&LRUCacheTest::TestAddWithOverflow));
suite.addTest(
new CppUnit::TestCaller<LRUCacheTest>(
"LRUCacheTest::TestAddWithOverflowWithGets",
&LRUCacheTest::TestAddWithOverflowWithGets));
suite.addTest(
new CppUnit::TestCaller<LRUCacheTest>(
"LRUCacheTest::TestRemove",
&LRUCacheTest::TestRemove));
parent.addTest("LRUCacheTest", &suite);
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2020, Andrew Lindesay <[email protected]>
* Distributed under the terms of the MIT License.
*/
#ifndef LRU_CACHE_TEST_H
#define LRU_CACHE_TEST_H
#include <TestCase.h>
#include <TestSuite.h>
class LRUCacheTest : public CppUnit::TestCase {
public:
LRUCacheTest();
virtual ~LRUCacheTest();
void TestAddWithOverflow();
void TestAddWithOverflowWithGets();
void TestRemove();
static void AddTests(BTestSuite& suite);
};
#endif // LRU_CACHE_TEST_H
@@ -17,6 +17,7 @@
#include "JsonTextWriterTest.h"
#include "JsonToMessageTest.h"
#include "KeymapTest.h"
#include "LRUCacheTest.h"
BTestSuite*
@@ -33,6 +34,7 @@ getTestSuite()
JsonTextWriterTest::AddTests(*suite);
JsonToMessageTest::AddTests(*suite);
KeymapTest::AddTests(*suite);
LRUCacheTest::AddTests(*suite);
return suite;
}