#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_GRAPHICS_HELPERS_LRUMAP_H #define NUCLEX_GRAPHICS_HELPERS_LRUMAP_H #include namespace Nuclex { namespace Graphics { namespace Helpers { // ------------------------------------------------------------------------------------------- // /// Map which also keeps track of which items were least recently used /// Key by which the map is indexed /// Type of value stored in the map /// Functor that will be used to hash the map's keys template< typename TKey, typename TValue, typename THash = std::hash > class LruMap { #pragma region struct Entry /// Stores an entry in the LruMap private: struct Entry { /// Initializes a new least recently used map entry /// Key the entry is stored under /// Value that has been stored in the least recently used map public: Entry(const TKey &key, const TValue &value) : Key(key), Value(value) {} /// Inserts the entry into the list before the specified other entry /// Entry before which this entry will be inserted public: void Link(Entry *before) { this->Next = before->Next; this->Previous = before; before->Next = this; this->Next->Previous = this; } /// Unlinks the entry from the map public: void Unlink() { this->Previous->Next = this->Next; this->Next->Previous = this->Previous; } /// Moves the entry to a new place in the list /// Other entry before which this entry will be moved public: void Move(Entry *before) { Unlink(); Link(before); } /// Key the entry is stored under public: TKey Key; /// Value carried by the entry public: TValue Value; /// Next least recently used entry in the map public: Entry *Previous; /// Next most recently used entry in the map public: Entry *Next; }; #pragma endregion // struct Entry /// Initializes a new least recently used map public: LruMap() : mostRecentlyUsed(nullptr) {} /// Frees all memory used by the least recently used map public: ~LruMap() { EntryMap::iterator iterator = this->entries.begin(); for(; iterator != this->entries.end(); ++iterator) { delete iterator->second; } } /// Attempts to retrieve and touch an entry in the map /// Key whose associated value will be retrieved /// Receives the value if the specified key was found /// True if the specified key was found public: bool TryGetAndTouch(const TKey &key, TValue &value) { EntryMap::iterator iterator = this->entries.find(key); if(iterator == this->entries.end()) { return false; } Entry *entry = iterator->second; entry->Move(this->mostRecentlyUsed); this->mostRecentlyUsed = entry; value = entry->Value; return true; } /// Adds an entry to the map /// Key under which the value can be looked up /// Value that will be added to the map public: void Add(const TKey &key, const TValue &value) { Entry *entry = new Entry(key, value); if(this->mostRecentlyUsed == nullptr) { entry->Previous = entry; entry->Next = entry; } else { entry->Link(this->mostRecentlyUsed); } this->mostRecentlyUsed = entry; this->entries.insert(std::make_pair(key, entry)); } /// Adds an entry to the map and trims the map to the specified size /// Key under which the value can be looked up /// Value that will be added to the map /// Maximum size the map is allowed to have /// /// Prefer this method to separately calling Add() and Trim() because it allows the /// map to reuse internal data structures instead of freeing and reallocating them. /// public: void AddAndTrim(const TKey &key, const TValue &value, std::size_t maximumSize) { std::size_t actualSize = this->entries.size(); if((actualSize == 0) || (actualSize < maximumSize)) { Add(key, value); } else { EntryMap::iterator iterator = this->entries.find(this->mostRecentlyUsed->Key); this->entries.erase(iterator); // Yep, since it's a circularly linked list, we can do this without adjusting // any pointers. Just imagine it like turning a wheel one notch backwards :) this->mostRecentlyUsed = this->mostRecentlyUsed->Previous; this->mostRecentlyUsed->Key = key; this->mostRecentlyUsed->Value = value; this->entries.insert(std::make_pair(key, this->mostRecentlyUsed)); } } /// Attempts to remove the entry with the specified key from the map /// Key of the entry that will be removed if found /// True if the key was found and the entry removed, false otherwise public: bool TryRemove(const TKey &key) { EntryMap::iterator iterator = this->entries.find(key); if(iterator == this->entries.end()) { return false; } Entry *entry = iterator->second; this->entries.erase(iterator); // If the removed entry was the most recently used one, we need to update // our most recently used variable. This will always be true when the last // entry is removed from the list. if(entry == this->mostRecentlyUsed) { if(this->entries.empty()) { this->mostRecentlyUsed = nullptr; } else { this->mostRecentlyUsed = entry->Next; entry->Unlink(); } } delete entry; } /// Reduces the size of the map to at most the specified number of items /// Maximal size the map is allowed to have public: void Trim(std::size_t maximumSize) { // If the list is empty, there's nothing to do (and the algorithm below would // blow up, incidentally) if(this->mostRecentlyUsed == nullptr) { return; } // Keep removing the least recently used entry until we hit our budget. We do // not care about list integrity at this point. Entry *leastRecentlyUsed = this->mostRecentlyUsed->Previous; while(this->entries.size() > maximumSize) { EntryMap::iterator iterator = this->entries.find(leastRecentlyUsed->Key); this->entries.erase(iterator); Entry *previous = leastRecentlyUsed->Previous; delete leastRecentlyUsed; leastRecentlyUsed = previous; } // If the entire map was emptied, there will be no entries left, so just clear // the LRU list. Otherwise, reestablish the loop between the last and the first entry. if(maximumSize == 0) { this->mostRecentlyUsed = nullptr; } else { leastRecentlyUsed->Next = this->mostRecentlyUsed; this->mostRecentlyUsed->Previous = leastRecentlyUsed; } } /// Removes all entries from the map public: void Clear() { EntryMap::iterator iterator = this->entries.begin(); for(; iterator != this->entries.end(); ++iterator) { delete iterator->second; } this->entries.clear(); this->mostRecentlyUsed = nullptr; } private: LruMap(const LruMap &); private: LruMap &operator =(const LruMap &); /// Map that associates keys with entries private: typedef std::unordered_map EntryMap; /// Stores the entries in the least recently used map private: EntryMap entries; /// Points to the most recently used entry in the map private: Entry *mostRecentlyUsed; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Graphics::Helpers #endif // NUCLEX_GRAPHICS_HELPERS_LRUMAP_H