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:
committed by
Adrien Destugues
parent
55d6d58d9f
commit
027d608682
@@ -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()
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ UnitTestLib libsharedtest.so :
|
||||
JsonToMessageTest.cpp
|
||||
GeolocationTest.cpp
|
||||
KeymapTest.cpp
|
||||
LRUCacheTest.cpp
|
||||
NaturalCompareTest.cpp
|
||||
|
||||
: be shared bnetapi [ TargetLibstdc++ ] [ TargetLibsupc++ ]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user