#pragma once #include #include #include #include #include // Cache that evicts old entries which have not been used recently. Implemented // using array/linear search so this works well for small array sizes. template struct LruCache { explicit LruCache(int max_entries); // Fetches an entry for |key|. If it does not exist, |allocator| will be // invoked to create one. template TValue Get(TKey const& key, TAllocator allocator); // Returns true if there is an entry for |key|. bool Has(TKey const& key); // Fetches the entry for |filename| and updates it's usage so it is less // likely to be evicted. bool TryGet(TKey const& key, TValue* dest); // TryGetEntry, except the entry is removed from the cache. bool TryTake(TKey const& key, TValue* dest); // Inserts an entry. Evicts the oldest unused entry if there is no space. void Insert(TKey const& key, TValue const& value); // Call |func| on existing entries. If |func| returns false iteration // terminates early. template void IterateValues(TFunc func); // Empties the cache void Clear(void); private: // There is a global score counter, when we access an element we increase // its score to the current global value, so it has the highest overall // score. This means that the oldest/least recently accessed value has the // lowest score. // // There is a bit of special logic to handle score overlow. struct Entry { uint32_t score = 0; TKey key; TValue value; bool operator<(Entry const& other) const { return score < other.score; } }; void IncrementScore(); std::vector entries_; int max_entries_ = 1; uint32_t next_score_ = 0; }; template LruCache::LruCache(int max_entries) : max_entries_(max_entries) { assert(max_entries > 0); } template template TValue LruCache::Get(TKey const& key, TAllocator allocator) { for (Entry& entry : entries_) { if (entry.key == key) { return entry.value; } } auto result = allocator(); Insert(key, result); return result; } template bool LruCache::Has(TKey const& key) { for (Entry& entry : entries_) { if (entry.key == key) { return true; } } return false; } template bool LruCache::TryGet(TKey const& key, TValue* dest) { // Assign new score. for (Entry& entry : entries_) { if (entry.key == key) { entry.score = next_score_; IncrementScore(); if (dest) { *dest = entry.value; } return true; } } return false; } template bool LruCache::TryTake(TKey const& key, TValue* dest) { for (size_t i = 0; i < entries_.size(); ++i) { if (entries_[i].key == key) { if (dest) { *dest = entries_[i].value; } entries_.erase(entries_.begin() + i); return true; } } return false; } template void LruCache::Insert(TKey const& key, TValue const& value) { if ((int)entries_.size() >= max_entries_) { entries_.erase(std::min_element(entries_.begin(), entries_.end())); } Entry entry; entry.score = next_score_; IncrementScore(); entry.key = key; entry.value = value; entries_.push_back(entry); } template template void LruCache::IterateValues(TFunc func) { for (Entry& entry : entries_) { if (!func(entry.value)) { break; } } } template void LruCache::IncrementScore() { // Overflow. if (++next_score_ == 0) { std::sort(entries_.begin(), entries_.end()); for (Entry& entry : entries_) { entry.score = next_score_++; } } } template void LruCache::Clear(void) { entries_.clear(); next_score_ = 0; }