#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 std::shared_ptr Get(const TKey& key, TAllocator allocator); // Fetches the entry for |filename| and updates it's usage so it is less // likely to be evicted. std::shared_ptr TryGet(const TKey& key); // TryGetEntry, except the entry is removed from the cache. std::shared_ptr TryTake(const TKey& key); // Inserts an entry. Evicts the oldest unused entry if there is no space. void Insert(const TKey& key, const std::shared_ptr& value); // Call |func| on existing entries. If |func| returns false iteration // temrinates early. template void IterateValues(TFunc func); 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; std::shared_ptr value; bool operator<(const Entry &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 std::shared_ptr LruCache::Get(const TKey& key, TAllocator allocator) { std::shared_ptr result = TryGet(key); if (!result) Insert(key, result = allocator()); return result; } template std::shared_ptr LruCache::TryGet(const TKey& key) { // Assign new score. for (Entry& entry : entries_) { if (entry.key == key) { entry.score = next_score_; IncrementScore(); return entry.value; } } return nullptr; } template std::shared_ptr LruCache::TryTake(const TKey& key) { for (size_t i = 0; i < entries_.size(); ++i) { if (entries_[i].key == key) { std::shared_ptr copy = entries_[i].value; entries_.erase(entries_.begin() + i); return copy; } } return nullptr; } template void LruCache::Insert(const TKey& key, const std::shared_ptr& value) { if (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_++; } }