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]>.
|
* Copyright 2020, Andrew Lindesay <[email protected]>.
|
||||||
* All rights reserved. Distributed under the terms of the MIT License.
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "PackageIconTarRepository.h"
|
#include "PackageIconTarRepository.h"
|
||||||
|
|
||||||
#include <Autolock.h>
|
#include <Autolock.h>
|
||||||
@@ -13,6 +15,9 @@
|
|||||||
#include "TarArchiveService.h"
|
#include "TarArchiveService.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define LIMIT_ICON_CACHE 50
|
||||||
|
|
||||||
|
|
||||||
BitmapRef
|
BitmapRef
|
||||||
PackageIconTarRepository::sDefaultIcon(new(std::nothrow) SharedBitmap(
|
PackageIconTarRepository::sDefaultIcon(new(std::nothrow) SharedBitmap(
|
||||||
"application/x-vnd.haiku-package"), true);
|
"application/x-vnd.haiku-package"), true);
|
||||||
@@ -123,7 +128,8 @@ IconTarPtrEntryListener::_LeafNameToBitmapSize(BString& leafName,
|
|||||||
|
|
||||||
PackageIconTarRepository::PackageIconTarRepository()
|
PackageIconTarRepository::PackageIconTarRepository()
|
||||||
:
|
:
|
||||||
fTarIo(NULL)
|
fTarIo(NULL),
|
||||||
|
fIconCache(LIMIT_ICON_CACHE)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#ifndef PACKAGE_ICON_TAR_REPOSITORY_H
|
#ifndef PACKAGE_ICON_TAR_REPOSITORY_H
|
||||||
#define PACKAGE_ICON_TAR_REPOSITORY_H
|
#define PACKAGE_ICON_TAR_REPOSITORY_H
|
||||||
|
|
||||||
|
|
||||||
#include <DataIO.h>
|
#include <DataIO.h>
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
#include <HashString.h>
|
#include <HashString.h>
|
||||||
@@ -12,8 +13,9 @@
|
|||||||
#include <Path.h>
|
#include <Path.h>
|
||||||
#include <Referenceable.h>
|
#include <Referenceable.h>
|
||||||
|
|
||||||
#include "PackageIconRepository.h"
|
|
||||||
#include "IconTarPtr.h"
|
#include "IconTarPtr.h"
|
||||||
|
#include "LRUCache.h"
|
||||||
|
#include "PackageIconRepository.h"
|
||||||
|
|
||||||
typedef BReference<IconTarPtr> IconTarPtrRef;
|
typedef BReference<IconTarPtr> IconTarPtrRef;
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ private:
|
|||||||
private:
|
private:
|
||||||
BLocker fLock;
|
BLocker fLock;
|
||||||
BPositionIO* fTarIo;
|
BPositionIO* fTarIo;
|
||||||
HashMap<HashString, BitmapRef>
|
LRUCache<HashString, BitmapRef>
|
||||||
fIconCache;
|
fIconCache;
|
||||||
HashMap<HashString, IconTarPtrRef>
|
HashMap<HashString, IconTarPtrRef>
|
||||||
fIconTarPtrs;
|
fIconTarPtrs;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <TestSuite.h>
|
#include <TestSuite.h>
|
||||||
#include <TestSuiteAddon.h>
|
#include <TestSuiteAddon.h>
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
#include "TarArchiveServiceTest.h"
|
#include "TarArchiveServiceTest.h"
|
||||||
#include "ListTest.h"
|
#include "ListTest.h"
|
||||||
|
|
||||||
|
|
||||||
BTestSuite*
|
BTestSuite*
|
||||||
getTestSuite()
|
getTestSuite()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ UnitTestLib libsharedtest.so :
|
|||||||
JsonToMessageTest.cpp
|
JsonToMessageTest.cpp
|
||||||
GeolocationTest.cpp
|
GeolocationTest.cpp
|
||||||
KeymapTest.cpp
|
KeymapTest.cpp
|
||||||
|
LRUCacheTest.cpp
|
||||||
NaturalCompareTest.cpp
|
NaturalCompareTest.cpp
|
||||||
|
|
||||||
: be shared bnetapi [ TargetLibstdc++ ] [ TargetLibsupc++ ]
|
: 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 "JsonTextWriterTest.h"
|
||||||
#include "JsonToMessageTest.h"
|
#include "JsonToMessageTest.h"
|
||||||
#include "KeymapTest.h"
|
#include "KeymapTest.h"
|
||||||
|
#include "LRUCacheTest.h"
|
||||||
|
|
||||||
|
|
||||||
BTestSuite*
|
BTestSuite*
|
||||||
@@ -33,6 +34,7 @@ getTestSuite()
|
|||||||
JsonTextWriterTest::AddTests(*suite);
|
JsonTextWriterTest::AddTests(*suite);
|
||||||
JsonToMessageTest::AddTests(*suite);
|
JsonToMessageTest::AddTests(*suite);
|
||||||
KeymapTest::AddTests(*suite);
|
KeymapTest::AddTests(*suite);
|
||||||
|
LRUCacheTest::AddTests(*suite);
|
||||||
|
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user