diff --git a/BH/CMakeLists.txt b/BH/CMakeLists.txt index 09e59660..45c326fa 100644 --- a/BH/CMakeLists.txt +++ b/BH/CMakeLists.txt @@ -50,4 +50,5 @@ set(DRAWING_SOURCES "Drawing/Hook.cpp" set(SOURCE_FILES ${SOURCE_FILES} ${MODULE_SOURCES} ${DRAWING_SOURCES}) add_library(BH ${SOURCE_FILES}) +target_include_directories(BH PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ThirdParty/cpp-lru-cache/include) target_link_libraries(BH ${STORM_LIBRARY} Shlwapi) diff --git a/BH/Common.cpp b/BH/Common.cpp index 789d9a95..d5bd0f51 100644 --- a/BH/Common.cpp +++ b/BH/Common.cpp @@ -123,11 +123,13 @@ int StringToNumber(std::string str) { return ret; } +// This function prints at most 151 characters (152 including null) +// TODO: Fix this so this limitation void PrintText(DWORD Color, char *szText, ...) { char szBuffer[152] = {0}; va_list Args; va_start(Args, szText); - vsprintf_s(szBuffer, 152, szText, Args); + vsnprintf_s(szBuffer, 152, _TRUNCATE, szText, Args); va_end(Args); wchar_t Buffer[0x130]; MultiByteToWideChar(CODE_PAGE, 1, szBuffer, 152, Buffer, 304); @@ -461,4 +463,4 @@ char *commaprint(unsigned long n) } while (n != 0); return p; -} \ No newline at end of file +} diff --git a/BH/Drawing/UI/UI.cpp b/BH/Drawing/UI/UI.cpp index 943f3cd5..a7c2fa3a 100644 --- a/BH/Drawing/UI/UI.cpp +++ b/BH/Drawing/UI/UI.cpp @@ -174,10 +174,10 @@ void UI::OnDraw() { } } -void UI::SetDragged(bool state) { +void UI::SetDragged(bool state, bool write_file) { Lock(); dragged = state; - if (!state) { + if (!state && write_file) { WritePrivateProfileString(name.c_str(), "X", to_string(GetX()).c_str(), string(BH::path + "UI.ini").c_str()); WritePrivateProfileString(name.c_str(), "Y", to_string(GetY()).c_str(), string(BH::path + "UI.ini").c_str()); WritePrivateProfileString(name.c_str(), "minimizedX", to_string(GetMinimizedX()).c_str(), string(BH::path + "UI.ini").c_str()); @@ -186,6 +186,10 @@ void UI::SetDragged(bool state) { Unlock(); } +void UI::SetDragged(bool state) { + SetDragged(state, false); +} + void UI::SetMinimized(bool newState) { if (newState == minimized) return; @@ -228,7 +232,7 @@ bool UI::OnLeftClick(bool up, unsigned int mouseX, unsigned int mouseY) { } else { - SetDragged(false); + SetDragged(false, true); if(!up) { PrintText(7, "CTRL-click to open settings" ); PrintText(7, "Shift-drag to move" ); @@ -248,7 +252,7 @@ bool UI::OnLeftClick(bool up, unsigned int mouseX, unsigned int mouseY) { } else { - SetDragged(false); + SetDragged(false, true); if( startX == mouseX && startY == mouseY && GetAsyncKeyState(VK_CONTROL) ) { PrintText(135, "Right Click to Close" ); @@ -271,7 +275,7 @@ bool UI::OnLeftClick(bool up, unsigned int mouseX, unsigned int mouseY) { return true; } SetActive(false); - SetDragged(false); + SetDragged(false, false); return false; } diff --git a/BH/Drawing/UI/UI.h b/BH/Drawing/UI/UI.h index d2e3a6fc..d8ccf9e3 100644 --- a/BH/Drawing/UI/UI.h +++ b/BH/Drawing/UI/UI.h @@ -55,7 +55,8 @@ namespace Drawing { void SetActive(bool newState) { Lock(); active = newState; Unlock(); }; void SetMinimized(bool newState); void SetName(std::string newName) { Lock(); name = newName; Unlock(); }; - void SetDragged(bool state); + void SetDragged(bool state, bool write_file); // only write config to file if write_file is true + void SetDragged(bool state); // never writes the config file void SetZOrder(unsigned int newZ) { Lock(); zOrder = newZ; Unlock(); }; UITab* GetActiveTab() { if (!currentTab) { currentTab = (*Tabs.begin()); } return currentTab; }; diff --git a/BH/Modules/Item/Item.cpp b/BH/Modules/Item/Item.cpp index 75c1205d..5236cb56 100644 --- a/BH/Modules/Item/Item.cpp +++ b/BH/Modules/Item/Item.cpp @@ -50,6 +50,7 @@ #include "../../D2Stubs.h" #include "ItemDisplay.h" #include "../../MPQInit.h" +#include "lrucache.hpp" ItemsTxtStat* GetAllStatModifier(ItemsTxtStat* pStats, int nStats, int nStat, ItemsTxtStat* pOrigin); ItemsTxtStat* GetItemsTxtStatByMod(ItemsTxtStat* pStats, int nStats, int nStat, int nStatParam); @@ -86,6 +87,13 @@ void Item::OnLoad() { DrawSettings(); } +void Item::OnGameJoin() { + // reset the item name cache upon joining games + // (GUIDs not unique across games) + item_name_cache.ResetCache(); + map_action_cache.ResetCache(); +} + void Item::LoadConfig() { BH::config->ReadToggle("Show Ethereal", "None", true, Toggles["Show Ethereal"]); BH::config->ReadToggle("Show Sockets", "None", true, Toggles["Show Sockets"]); diff --git a/BH/Modules/Item/Item.h b/BH/Modules/Item/Item.h index 9b037248..07cd103a 100644 --- a/BH/Modules/Item/Item.h +++ b/BH/Modules/Item/Item.h @@ -67,6 +67,8 @@ class Item : public Module { void LoadConfig(); void DrawSettings(); + void OnGameJoin(); + void OnLoop(); void OnKey(bool up, BYTE key, LPARAM lParam, bool* block); void OnLeftClick(bool up, int x, int y, bool* block); diff --git a/BH/Modules/Item/ItemDisplay.cpp b/BH/Modules/Item/ItemDisplay.cpp index 9835a83d..1cf860a6 100644 --- a/BH/Modules/Item/ItemDisplay.cpp +++ b/BH/Modules/Item/ItemDisplay.cpp @@ -151,15 +151,55 @@ BYTE RuneNumberFromItemCode(char *code){ return (BYTE)(((code[1] - '0') * 10) + code[2] - '0'); } -void GetItemName(UnitItemInfo *uInfo, string &name) { - for (vector::iterator it = RuleList.begin(); it != RuleList.end(); it++) { +// Find the item name. This code is called only when there's a cache miss +string ItemNameLookupCache::make_cached_T(UnitItemInfo *uInfo, const string &name) { + string new_name(name); + for (vector::const_iterator it = RuleList.begin(); it != RuleList.end(); it++) { if ((*it)->Evaluate(uInfo, NULL)) { - SubstituteNameVariables(uInfo, name, &(*it)->action); + SubstituteNameVariables(uInfo, new_name, &(*it)->action); if ((*it)->action.stopProcessing) { break; } } } + return new_name; +} + +string ItemNameLookupCache::to_str(const string &name) { + size_t start_pos = 0; + std::string itemName(name); + while ((start_pos = itemName.find('\n', start_pos)) != std::string::npos) { + itemName.replace(start_pos, 1, " - "); + start_pos += 3; + } + return itemName; +} + +vector MapActionLookupCache::make_cached_T(UnitItemInfo *uInfo) { + vector actions; + for (vector::const_iterator it = RuleList.begin(); it != RuleList.end(); it++) { + if ((*it)->Evaluate(uInfo, NULL)) { + actions.push_back((*it)->action); + } + } + return actions; +} + +string MapActionLookupCache::to_str(const vector &actions) { + string name; + for (auto &action : actions) { + name += action.name + " "; + } + return name; +} + +// least recently used cache for storing a limited number of item names +ItemNameLookupCache item_name_cache(RuleList); +MapActionLookupCache map_action_cache(MapRuleList); + +void GetItemName(UnitItemInfo *uInfo, string &name) { + string new_name = item_name_cache.Get(uInfo, name); + name.assign(new_name); } void SubstituteNameVariables(UnitItemInfo *uInfo, string &name, Action *action) { @@ -333,6 +373,8 @@ namespace ItemDisplay { item_display_initialized = true; rules.clear(); + item_name_cache.ResetCache(); + map_action_cache.ResetCache(); BH::config->ReadMapList("ItemDisplay", rules); for (unsigned int i = 0; i < rules.size(); i++) { string buf; @@ -365,6 +407,8 @@ namespace ItemDisplay { void UninitializeItemRules() { item_display_initialized = false; + item_name_cache.ResetCache(); + map_action_cache.ResetCache(); RuleList.clear(); MapRuleList.clear(); IgnoreRuleList.clear(); diff --git a/BH/Modules/Item/ItemDisplay.h b/BH/Modules/Item/ItemDisplay.h index 8a36f37b..34647a4e 100644 --- a/BH/Modules/Item/ItemDisplay.h +++ b/BH/Modules/Item/ItemDisplay.h @@ -6,6 +6,7 @@ #include "../../BH.h" #include #include +#include "../../RuleLookupCache.h" #define EXCEPTION_INVALID_STAT 1 #define EXCEPTION_INVALID_OPERATION 2 @@ -576,10 +577,26 @@ struct Rule { } }; +class ItemNameLookupCache : public RuleLookupCache { + string make_cached_T(UnitItemInfo *uInfo, const string &name) override; + string to_str(const string &name) override; + + using RuleLookupCache::RuleLookupCache; +}; + +class MapActionLookupCache : public RuleLookupCache> { + vector make_cached_T(UnitItemInfo *uInfo) override; + string to_str(const vector &actions); + + using RuleLookupCache::RuleLookupCache; +}; + extern vector RuleList; extern vector MapRuleList; extern vector IgnoreRuleList; extern vector> rules; +extern ItemNameLookupCache item_name_cache; +extern MapActionLookupCache map_action_cache; namespace ItemDisplay { void InitializeItemRules(); diff --git a/BH/Modules/Maphack/Maphack.cpp b/BH/Modules/Maphack/Maphack.cpp index cac7c7ca..41377a0b 100644 --- a/BH/Modules/Maphack/Maphack.cpp +++ b/BH/Modules/Maphack/Maphack.cpp @@ -61,18 +61,18 @@ void Maphack::ReadConfig() { BH::config->ReadAssoc("Missile Color", missileColors); BH::config->ReadAssoc("Monster Color", monsterColors); - TextColorMap["�c0"] = 0x20; // white - TextColorMap["�c1"] = 0x0A; // red - TextColorMap["�c2"] = 0x84; // green - TextColorMap["�c3"] = 0x97; // blue - TextColorMap["�c4"] = 0x0D; // gold - TextColorMap["�c5"] = 0xD0; // gray - TextColorMap["�c6"] = 0x00; // black - TextColorMap["�c7"] = 0x5A; // tan - TextColorMap["�c8"] = 0x60; // orange - TextColorMap["�c9"] = 0x0C; // yellow - TextColorMap["�c;"] = 0x9B; // purple - TextColorMap["�c:"] = 0x76; // dark green + TextColorMap["ÿc0"] = 0x20; // white + TextColorMap["ÿc1"] = 0x0A; // red + TextColorMap["ÿc2"] = 0x84; // green + TextColorMap["ÿc3"] = 0x97; // blue + TextColorMap["ÿc4"] = 0x0D; // gold + TextColorMap["ÿc5"] = 0xD0; // gray + TextColorMap["ÿc6"] = 0x00; // black + TextColorMap["ÿc7"] = 0x5A; // tan + TextColorMap["ÿc8"] = 0x60; // orange + TextColorMap["ÿc9"] = 0x0C; // yellow + TextColorMap["ÿc;"] = 0x9B; // purple + TextColorMap["ÿc:"] = 0x76; // dark green BH::config->ReadAssoc("Monster Color", MonsterColors); for (auto it = MonsterColors.cbegin(); it != MonsterColors.cend(); it++) { @@ -418,8 +418,8 @@ void Maphack::OnAutomapDraw() { } //Determine immunities - string szImmunities[] = { "�c7i", "�c8i", "�c1i", "�c9i", "�c3i", "�c2i" }; - string szResistances[] = { "�c7r", "�c8r", "�c1r", "�c9r", "�c3r", "�c2r" }; + string szImmunities[] = { "ÿc7i", "ÿc8i", "ÿc1i", "ÿc9i", "ÿc3i", "ÿc2i" }; + string szResistances[] = { "ÿc7r", "ÿc8r", "ÿc1r", "ÿc9r", "ÿc3r", "ÿc2r" }; DWORD dwImmunities[] = { STAT_DMGREDUCTIONPCT, STAT_MAGICDMGREDUCTIONPCT, @@ -442,7 +442,7 @@ void Maphack::OnAutomapDraw() { //Determine Enchantments string enchantText; if (Toggles["Monster Enchantments"].state) { - string szEnchantments[] = {"�c3m", "�c1e", "�c9e", "�c3e"}; + string szEnchantments[] = {"ÿc3m", "ÿc1e", "ÿc9e", "ÿc3e"}; for (int n = 0; n < 9; n++) { if (unit->pMonsterData->fBoss) { @@ -512,33 +512,32 @@ void Maphack::OnAutomapDraw() { uInfo.itemCode[3] = 0; if (ItemAttributeMap.find(uInfo.itemCode) != ItemAttributeMap.end()) { uInfo.attrs = ItemAttributeMap[uInfo.itemCode]; - for (vector::iterator it = MapRuleList.begin(); it != MapRuleList.end(); it++) { - if ((*it)->Evaluate(&uInfo, NULL)) { - auto color = (*it)->action.colorOnMap; - auto borderColor = (*it)->action.borderColor; - auto dotColor = (*it)->action.dotColor; - auto pxColor = (*it)->action.pxColor; - auto lineColor = (*it)->action.lineColor; - xPos = unit->pItemPath->dwPosX; - yPos = unit->pItemPath->dwPosY; - automapBuffer.push_top_layer( - [color, unit, xPos, yPos, MyPos, borderColor, dotColor, pxColor, lineColor]()->void{ - POINT automapLoc; - Drawing::Hook::ScreenToAutomap(&automapLoc, xPos, yPos); - if (borderColor != UNDEFINED_COLOR) - Drawing::Boxhook::Draw(automapLoc.x - 4, automapLoc.y - 4, 8, 8, borderColor, Drawing::BTHighlight); - if (color != UNDEFINED_COLOR) - Drawing::Boxhook::Draw(automapLoc.x - 3, automapLoc.y - 3, 6, 6, color, Drawing::BTHighlight); - if (dotColor != UNDEFINED_COLOR) - Drawing::Boxhook::Draw(automapLoc.x - 2, automapLoc.y - 2, 4, 4, dotColor, Drawing::BTHighlight); - if (pxColor != UNDEFINED_COLOR) - Drawing::Boxhook::Draw(automapLoc.x - 1, automapLoc.y - 1, 2, 2, pxColor, Drawing::BTHighlight); - if (lineColor != UNDEFINED_COLOR) { - Drawing::Linehook::Draw(MyPos.x, MyPos.y, automapLoc.x, automapLoc.y, lineColor); - } - }); - if ((*it)->action.stopProcessing) break; - } + const vector actions = map_action_cache.Get(&uInfo); + for (auto &action : actions) { + auto color = action.colorOnMap; + auto borderColor = action.borderColor; + auto dotColor = action.dotColor; + auto pxColor = action.pxColor; + auto lineColor = action.lineColor; + xPos = unit->pItemPath->dwPosX; + yPos = unit->pItemPath->dwPosY; + automapBuffer.push_top_layer( + [color, unit, xPos, yPos, MyPos, borderColor, dotColor, pxColor, lineColor]()->void{ + POINT automapLoc; + Drawing::Hook::ScreenToAutomap(&automapLoc, xPos, yPos); + if (borderColor != UNDEFINED_COLOR) + Drawing::Boxhook::Draw(automapLoc.x - 4, automapLoc.y - 4, 8, 8, borderColor, Drawing::BTHighlight); + if (color != UNDEFINED_COLOR) + Drawing::Boxhook::Draw(automapLoc.x - 3, automapLoc.y - 3, 6, 6, color, Drawing::BTHighlight); + if (dotColor != UNDEFINED_COLOR) + Drawing::Boxhook::Draw(automapLoc.x - 2, automapLoc.y - 2, 4, 4, dotColor, Drawing::BTHighlight); + if (pxColor != UNDEFINED_COLOR) + Drawing::Boxhook::Draw(automapLoc.x - 1, automapLoc.y - 1, 2, 2, pxColor, Drawing::BTHighlight); + if (lineColor != UNDEFINED_COLOR) { + Drawing::Linehook::Draw(MyPos.x, MyPos.y, automapLoc.x, automapLoc.y, lineColor); + } + }); + if (action.stopProcessing) break; } } else { @@ -577,7 +576,7 @@ void Maphack::OnAutomapDraw() { return; for (list::iterator it = automapLevels.begin(); it != automapLevels.end(); it++) { if (player->pAct->dwAct == (*it)->act) { - string tombStar = ((*it)->levelId == player->pAct->pMisc->dwStaffTombLevel) ? "�c2*" : "�c4"; + string tombStar = ((*it)->levelId == player->pAct->pMisc->dwStaffTombLevel) ? "ÿc2*" : "ÿc4"; POINT unitLoc; Hook::ScreenToAutomap(&unitLoc, (*it)->x, (*it)->y); char* name = UnicodeToAnsi(D2CLIENT_GetLevelName((*it)->levelId)); @@ -661,13 +660,13 @@ void Maphack::OnGamePacketRecv(BYTE *packet, bool *block) { // if(packet[6+(packet[0]-0xa7)] == 100) { // UnitAny* pUnit = D2CLIENT_FindServerSideUnit(*(DWORD*)&packet[2], 0); // if(pUnit) - // PrintText(1, "Alert: �c4Player �c2%s �c4drank a �c1Health �c4potion!", pUnit->pPlayerData->szName); + // PrintText(1, "Alert: ÿc4Player ÿc2%s ÿc4drank a ÿc1Health ÿc4potion!", pUnit->pPlayerData->szName); // } else if (packet[6+(packet[0]-0xa7)] == 105) { // UnitAny* pUnit = D2CLIENT_FindServerSideUnit(*(DWORD*)&packet[2], 0); // if(pUnit) // if(pUnit->dwTxtFileNo == 1) // if(D2COMMON_GetUnitState(pUnit, 30)) - // PrintText(1, "Alert: �c4ES Sorc �c2%s �c4drank a �c3Mana �c4Potion!", pUnit->pPlayerData->szName); + // PrintText(1, "Alert: ÿc4ES Sorc ÿc2%s ÿc4drank a ÿc3Mana ÿc4Potion!", pUnit->pPlayerData->szName); // } else if (packet[6+(packet[0]-0xa7)] == 102) {//remove portal delay // *block = true; // } @@ -928,8 +927,8 @@ int HoverObjectPatch(UnitAny* pUnit, DWORD tY, DWORD unk1, DWORD unk2, DWORD tX, POINT p = Texthook::GetTextSize(wTxt, 1); int center = tX + (p.x / 2); int y = tY - p.y; - Texthook::Draw(center, y - 12, Center, 6, White, L"�c7%d �c8%d �c1%d �c9%d �c3%d �c2%d", dwResistances[0], dwResistances[1], dwResistances[2], dwResistances[3], dwResistances[4], dwResistances[5]); - Texthook::Draw(center, y, Center, 6, White, L"�c%d%s", HoverMonsterColor(pUnit), wTxt); + Texthook::Draw(center, y - 12, Center, 6, White, L"ÿc7%d ÿc8%d ÿc1%d ÿc9%d ÿc3%d ÿc2%d", dwResistances[0], dwResistances[1], dwResistances[2], dwResistances[3], dwResistances[4], dwResistances[5]); + Texthook::Draw(center, y, Center, 6, White, L"ÿc%d%s", HoverMonsterColor(pUnit), wTxt); Texthook::Draw(center, y + 8, Center, 6, White, L"%.0f%%", (hp / maxhp) * 100.0); return 1; } diff --git a/BH/RuleLookupCache.h b/BH/RuleLookupCache.h new file mode 100644 index 00000000..40414e82 --- /dev/null +++ b/BH/RuleLookupCache.h @@ -0,0 +1,86 @@ +// A cache wrapper class useful for inserting caches around rule list lookups + +#ifndef RULE_LOOKUP_CACHE_H_ +#define RULE_LOOKUP_CACHE_H_ + +extern struct UnitItemInfo; +extern struct Rule; + +#include +#include +#include +#include "lrucache.hpp" + +template +class RuleLookupCache { + std::unique_ptr>> cache; + + protected: + const std::vector &RuleList; + virtual T make_cached_T(UnitItemInfo *uInfo, Args&&... pack) = 0; + virtual std::string to_str(const T &cached_T) { + // This function only needs to be implemented for debug printing + return std::string("???"); + } + + public: + RuleLookupCache(const std::vector &rule_list) + : RuleList(rule_list), cache(new cache::lru_cache>(100)) {} + + void ResetCache() { + //PrintText(1, "Reseting LRU cache."); + cache.reset(new cache::lru_cache>(100)); + } + + // TODO: UnitItemInfo should probably be const, but call to Evaluate needs non-const + T Get(UnitItemInfo *uInfo, Args&&... pack) { + // leave this false. doesn't work + constexpr bool cache_debug = false; + static DWORD last_printed_guid = 0; // to prevent excessive printing + DWORD guid = uInfo->item->dwUnitId; // global unique identifier + // TODO: should we also use fingerprint or seed? Currently we trigger cache updates based + // on item flag changes. This should cover everything that I can think of, including IDing + // items, crafting items, making runewords, etc. Still would be nice to get some reassurance + // that GUIDs aren't reused in some unexpected way. Having a cache that is wrong is no bueno. + DWORD flags = uInfo->item->pItemData->dwFlags; + DWORD orig_cached_flags; // the cached flags + T cached_T; // the cached T after rules applied + bool cache_hit = false; + // First check if the name exists in the cache + if (cache && cache->exists(guid)) { + orig_cached_flags = cache->get(guid).first; + if (orig_cached_flags == flags) { + cached_T = cache->get(guid).second; + cache_hit = true; // needed because empty string is also a valid item name + } else { + // This print can give a hint if the GUID of an item ever changes. Problem is that it will also + // print whenever you ID an item, make a runeword, personalize an item, etc. + // Even seems to change when items get 'old'. + //PrintText(1, "Detected change in item flags. Cached: %x Actual: %x", orig_cached_flags, flags); + //PrintText(1, " Cached name str: %s", to_str(cache->get(guid).second).c_str()); + } + // cache_hit is false if the unmodified item name has changed from cached version + } + if (!cache_hit || cache_debug) { + cached_T = make_cached_T(uInfo, pack...); + if (cache && !cache_hit) { + std::pair pair_to_cache(flags, cached_T); + cache->put(guid, pair_to_cache); + //PrintText(1, "Adding key value pair %u, (%s, %x) to cache.", guid, to_str(cached_T).c_str(), flags); + } + // Debug stuff below doesn't work anymore because there's no general comparison function implemented + //else if (cached_name != name) { + // only runs if item_name_debug is on + // if (guid != last_printed_guid) { + // PrintText(1, "Mismatch in modified item name! Cached: %s Actual: %s", cached_name.c_str(), name.c_str()); + // last_printed_guid = guid; + // } + //} + } else { + return cached_T; + } + + } +}; + +#endif // RULE_LOOKUP_CACHE_H_ diff --git a/README.md b/README.md index 48382543..c73f923c 100644 --- a/README.md +++ b/README.md @@ -600,6 +600,6 @@ Which renders as: # Building -To build with CMake, first install "Visual Studio Build Tools 2017" and a version of CMake>=3.7. Visual Studio Build Tools comes with a "Developer Command Prompt" that sets up the path with the right compilers and build tools. Next, create a build directory within the project root directory and make it the current working directory. Then, run the command `cmake -G"Visual Studio 15 2017" -DCMAKE_BUILD_SHARED_LIBS=TRUE -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE ..` (save this command as a bat script if you like). This will create all necessary build files. Next, run `cmake --build . --config Release` to build the project. +To build with CMake, first install "Visual Studio Build Tools 2017" and a version of CMake>=3.7. Visual Studio Build Tools comes with a "Developer Command Prompt" that sets up the path with the right compilers and build tools. Next, create a build directory within the project root directory and make it the current working directory. Then, run the command `cmake -G"Visual Studio 15 2017" -DBUILD_SHARED_LIBS=TRUE -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE ..` (save this command as a bat script if you like). This will create all necessary build files. Next, run `cmake --build . --config Release` to build the project. To enable multi-processor support when buildling, set the CXXFLAGS environment variable with `set CXXFLAGS=/MP` prior to running the cmake command above. diff --git a/ThirdParty/cpp-lru-cache/.gitignore b/ThirdParty/cpp-lru-cache/.gitignore new file mode 100644 index 00000000..e4ea9919 --- /dev/null +++ b/ThirdParty/cpp-lru-cache/.gitignore @@ -0,0 +1,11 @@ +CMakeCache.txt +CMakeFiles +Makefile +install_manifest.txt +cmake_install.cmake + +nbproject +build + +cpp-lru-cache-test +obj-x86_64-linux-gnu/ diff --git a/ThirdParty/cpp-lru-cache/.travis.yml b/ThirdParty/cpp-lru-cache/.travis.yml new file mode 100644 index 00000000..a34c68a4 --- /dev/null +++ b/ThirdParty/cpp-lru-cache/.travis.yml @@ -0,0 +1,13 @@ +language: cpp + +compiler: + - gcc + +script: + - mkdir build + - cd build + - cmake .. + - make check + +notifications: + email: false diff --git a/ThirdParty/cpp-lru-cache/CMakeLists.txt b/ThirdParty/cpp-lru-cache/CMakeLists.txt new file mode 100644 index 00000000..f4e6f62b --- /dev/null +++ b/ThirdParty/cpp-lru-cache/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.8) + +project(CPP-LRU_CACHE) + +find_package(Threads REQUIRED) + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +set(EXT_PROJECTS_DIR ${PROJECT_SOURCE_DIR}/ext) +add_subdirectory(${EXT_PROJECTS_DIR}/gtest) + +enable_testing() + +include_directories( + ${GTEST_INCLUDE_DIRS} + ${PROJECT_SOURCE_DIR}/include) + +add_executable( + cpp-lru-cache-test + src/test) + +add_dependencies(cpp-lru-cache-test googletest) + +target_link_libraries( + cpp-lru-cache-test + ${GTEST_LIBS_DIR}/libgtest.a + ${GTEST_LIBS_DIR}/libgtest_main.a + ${CMAKE_THREAD_LIBS_INIT}) + +set_target_properties(cpp-lru-cache-test PROPERTIES + PREFIX "" + SUFFIX "" + COMPILE_FLAGS "-std=c++0x -W -Wall -pedantic") + +set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3") +set(CMAKE_CSS_FLAGS_RELEASE "-03 -g") + +add_test( + NAME cpp-lru-cache-test + COMMAND cpp-lru-cache-test +) + +add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --verbose + DEPENDS cpp-lru-cache-test) diff --git a/ThirdParty/cpp-lru-cache/LICENSE b/ThirdParty/cpp-lru-cache/LICENSE new file mode 100644 index 00000000..bc39bb83 --- /dev/null +++ b/ThirdParty/cpp-lru-cache/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014, lamerman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of lamerman nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ThirdParty/cpp-lru-cache/README.md b/ThirdParty/cpp-lru-cache/README.md new file mode 100644 index 00000000..9236d534 --- /dev/null +++ b/ThirdParty/cpp-lru-cache/README.md @@ -0,0 +1,29 @@ +cpp-lru-cache +============= + +Simple and reliable LRU (Least Recently Used) cache for c++ based on hashmap and linkedlist. The library is header only, simple test and example are included. +It includes standard components and very little own logics that guarantees reliability. + +Example: + +``` +/**Creates cache with maximum size of three. When the + size in achieved every next element will replace the + least recently used one */ +cache::lru_cache cache(3); + +cache.put("one", "one"); +cache.put("two", "two"); + +const std::string& from_cache = cache.get("two") + +``` + +How to run tests: + +``` +mkdir build +cd build +cmake .. +make check +``` diff --git a/ThirdParty/cpp-lru-cache/ext/gtest/CMakeLists.txt b/ThirdParty/cpp-lru-cache/ext/gtest/CMakeLists.txt new file mode 100644 index 00000000..44998e90 --- /dev/null +++ b/ThirdParty/cpp-lru-cache/ext/gtest/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 2.8) +project(gtest_builder C CXX) +include(ExternalProject) + +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.7.0 + CMAKE_ARGS -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs + -DCMAKE_CXX_FLAGS=${MSVC_COMPILER_DEFS} + -Dgtest_force_shared_crt=ON + PREFIX "${CMAKE_CURRENT_BINARY_DIR}" + INSTALL_COMMAND "" + UPDATE_COMMAND "" +) + +ExternalProject_Get_Property(googletest source_dir) +set(GTEST_INCLUDE_DIRS ${source_dir}/include PARENT_SCOPE) + +ExternalProject_Get_Property(googletest binary_dir) +set(GTEST_LIBS_DIR ${binary_dir} PARENT_SCOPE) diff --git a/ThirdParty/cpp-lru-cache/include/lrucache.hpp b/ThirdParty/cpp-lru-cache/include/lrucache.hpp new file mode 100644 index 00000000..9e5e2850 --- /dev/null +++ b/ThirdParty/cpp-lru-cache/include/lrucache.hpp @@ -0,0 +1,72 @@ +/* + * File: lrucache.hpp + * Author: Alexander Ponomarev + * + * Created on June 20, 2013, 5:09 PM + */ + +#ifndef _LRUCACHE_HPP_INCLUDED_ +#define _LRUCACHE_HPP_INCLUDED_ + +#include +#include +#include +#include + +namespace cache { + +template +class lru_cache { +public: + typedef typename std::pair key_value_pair_t; + typedef typename std::list::iterator list_iterator_t; + + lru_cache(size_t max_size) : + _max_size(max_size) { + } + + void put(const key_t& key, const value_t& value) { + auto it = _cache_items_map.find(key); + _cache_items_list.push_front(key_value_pair_t(key, value)); + if (it != _cache_items_map.end()) { + _cache_items_list.erase(it->second); + _cache_items_map.erase(it); + } + _cache_items_map[key] = _cache_items_list.begin(); + + if (_cache_items_map.size() > _max_size) { + auto last = _cache_items_list.end(); + last--; + _cache_items_map.erase(last->first); + _cache_items_list.pop_back(); + } + } + + const value_t& get(const key_t& key) { + auto it = _cache_items_map.find(key); + if (it == _cache_items_map.end()) { + throw std::range_error("There is no such key in cache"); + } else { + _cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second); + return it->second->second; + } + } + + bool exists(const key_t& key) const { + return _cache_items_map.find(key) != _cache_items_map.end(); + } + + size_t size() const { + return _cache_items_map.size(); + } + +private: + std::list _cache_items_list; + std::unordered_map _cache_items_map; + size_t _max_size; +}; + +} // namespace cache + +#endif /* _LRUCACHE_HPP_INCLUDED_ */ + diff --git a/ThirdParty/cpp-lru-cache/src/test.cpp b/ThirdParty/cpp-lru-cache/src/test.cpp new file mode 100644 index 00000000..e547a78e --- /dev/null +++ b/ThirdParty/cpp-lru-cache/src/test.cpp @@ -0,0 +1,45 @@ +#include "lrucache.hpp" +#include "gtest/gtest.h" + +const int NUM_OF_TEST1_RECORDS = 100; +const int NUM_OF_TEST2_RECORDS = 100; +const int TEST2_CACHE_CAPACITY = 50; + +TEST(CacheTest, SimplePut) { + cache::lru_cache cache_lru(1); + cache_lru.put(7, 777); + EXPECT_TRUE(cache_lru.exists(7)); + EXPECT_EQ(777, cache_lru.get(7)); + EXPECT_EQ(1, cache_lru.size()); +} + +TEST(CacheTest, MissingValue) { + cache::lru_cache cache_lru(1); + EXPECT_THROW(cache_lru.get(7), std::range_error); +} + +TEST(CacheTest1, KeepsAllValuesWithinCapacity) { + cache::lru_cache cache_lru(TEST2_CACHE_CAPACITY); + + for (int i = 0; i < NUM_OF_TEST2_RECORDS; ++i) { + cache_lru.put(i, i); + } + + for (int i = 0; i < NUM_OF_TEST2_RECORDS - TEST2_CACHE_CAPACITY; ++i) { + EXPECT_FALSE(cache_lru.exists(i)); + } + + for (int i = NUM_OF_TEST2_RECORDS - TEST2_CACHE_CAPACITY; i < NUM_OF_TEST2_RECORDS; ++i) { + EXPECT_TRUE(cache_lru.exists(i)); + EXPECT_EQ(i, cache_lru.get(i)); + } + + size_t size = cache_lru.size(); + EXPECT_EQ(TEST2_CACHE_CAPACITY, size); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + int ret = RUN_ALL_TESTS(); + return ret; +}