diff --git a/headers/private/shared/LRUCache.h b/headers/private/shared/LRUCache.h new file mode 100644 index 0000000000..57f35afcfa --- /dev/null +++ b/headers/private/shared/LRUCache.h @@ -0,0 +1,208 @@ +/* + * Copyright 2020, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef LRU_CACHE_H +#define LRU_CACHE_H + + +#include + + +namespace BPrivate { + + +template +class LRUOrderingNode { +private: + typedef LRUOrderingNode 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 +class LRUCache { +public: + typedef LRUOrderingNode 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 fMap; + LRUNode* fNewestNode; + LRUNode* fOldestNode; + +private: + int32 fLimit; +}; + +}; // namespace BPrivate + +using BPrivate::LRUCache; + +#endif // LRU_CACHE_H diff --git a/src/apps/haikudepot/model/PackageIconTarRepository.cpp b/src/apps/haikudepot/model/PackageIconTarRepository.cpp index 3f872c8153..fa69d6d94a 100644 --- a/src/apps/haikudepot/model/PackageIconTarRepository.cpp +++ b/src/apps/haikudepot/model/PackageIconTarRepository.cpp @@ -2,6 +2,8 @@ * Copyright 2020, Andrew Lindesay . * All rights reserved. Distributed under the terms of the MIT License. */ + + #include "PackageIconTarRepository.h" #include @@ -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) { } diff --git a/src/apps/haikudepot/model/PackageIconTarRepository.h b/src/apps/haikudepot/model/PackageIconTarRepository.h index 1ae509e4b3..ea0db5e982 100644 --- a/src/apps/haikudepot/model/PackageIconTarRepository.h +++ b/src/apps/haikudepot/model/PackageIconTarRepository.h @@ -5,6 +5,7 @@ #ifndef PACKAGE_ICON_TAR_REPOSITORY_H #define PACKAGE_ICON_TAR_REPOSITORY_H + #include #include #include @@ -12,8 +13,9 @@ #include #include -#include "PackageIconRepository.h" #include "IconTarPtr.h" +#include "LRUCache.h" +#include "PackageIconRepository.h" typedef BReference IconTarPtrRef; @@ -54,7 +56,7 @@ private: private: BLocker fLock; BPositionIO* fTarIo; - HashMap + LRUCache fIconCache; HashMap fIconTarPtrs; diff --git a/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp b/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp index 8ed7271540..69fe2abb96 100644 --- a/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp +++ b/src/tests/apps/haikudepot/HaikuDepotTestAddon.cpp @@ -3,6 +3,7 @@ * Distributed under the terms of the MIT License. */ + #include #include @@ -14,6 +15,7 @@ #include "TarArchiveServiceTest.h" #include "ListTest.h" + BTestSuite* getTestSuite() { diff --git a/src/tests/kits/shared/Jamfile b/src/tests/kits/shared/Jamfile index 05ef9c5e0c..586962b113 100644 --- a/src/tests/kits/shared/Jamfile +++ b/src/tests/kits/shared/Jamfile @@ -15,6 +15,7 @@ UnitTestLib libsharedtest.so : JsonToMessageTest.cpp GeolocationTest.cpp KeymapTest.cpp + LRUCacheTest.cpp NaturalCompareTest.cpp : be shared bnetapi [ TargetLibstdc++ ] [ TargetLibsupc++ ] diff --git a/src/tests/kits/shared/LRUCacheTest.cpp b/src/tests/kits/shared/LRUCacheTest.cpp new file mode 100644 index 0000000000..11e3428cfc --- /dev/null +++ b/src/tests/kits/shared/LRUCacheTest.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2020, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ + + +#include "LRUCacheTest.h" + +#include + +#include +#include + +#include +#include + +#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 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 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 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::TestAddWithOverflow", + &LRUCacheTest::TestAddWithOverflow)); + suite.addTest( + new CppUnit::TestCaller( + "LRUCacheTest::TestAddWithOverflowWithGets", + &LRUCacheTest::TestAddWithOverflowWithGets)); + suite.addTest( + new CppUnit::TestCaller( + "LRUCacheTest::TestRemove", + &LRUCacheTest::TestRemove)); + + parent.addTest("LRUCacheTest", &suite); +} diff --git a/src/tests/kits/shared/LRUCacheTest.h b/src/tests/kits/shared/LRUCacheTest.h new file mode 100644 index 0000000000..7518e161cd --- /dev/null +++ b/src/tests/kits/shared/LRUCacheTest.h @@ -0,0 +1,26 @@ +/* + * Copyright 2020, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef LRU_CACHE_TEST_H +#define LRU_CACHE_TEST_H + + +#include +#include + + +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 diff --git a/src/tests/kits/shared/SharedTestAddon.cpp b/src/tests/kits/shared/SharedTestAddon.cpp index a671858116..b7f892a9cb 100644 --- a/src/tests/kits/shared/SharedTestAddon.cpp +++ b/src/tests/kits/shared/SharedTestAddon.cpp @@ -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; }