| // |
| // Copyright 2017 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // SizedMRUCache.h: A hashing map that stores blobs of sized, untyped data. |
| |
| #ifndef LIBANGLE_SIZED_MRU_CACHE_H_ |
| #define LIBANGLE_SIZED_MRU_CACHE_H_ |
| |
| #include <anglebase/containers/mru_cache.h> |
| |
| namespace angle |
| { |
| |
| template <typename Key, typename Value> |
| class SizedMRUCache final : angle::NonCopyable |
| { |
| public: |
| SizedMRUCache(size_t maximumTotalSize) |
| : mMaximumTotalSize(maximumTotalSize), |
| mCurrentSize(0), |
| mStore(SizedMRUCacheStore::NO_AUTO_EVICT) |
| {} |
| |
| // Returns nullptr on failure. |
| const Value *put(const Key &key, Value &&value, size_t size) |
| { |
| if (size > mMaximumTotalSize) |
| { |
| return nullptr; |
| } |
| |
| // Check for existing key. |
| eraseByKey(key); |
| |
| auto retVal = mStore.Put(key, ValueAndSize(std::move(value), size)); |
| mCurrentSize += size; |
| |
| shrinkToSize(mMaximumTotalSize); |
| |
| return &retVal->second.value; |
| } |
| |
| bool get(const Key &key, const Value **valueOut) |
| { |
| const auto &iter = mStore.Get(key); |
| if (iter == mStore.end()) |
| { |
| return false; |
| } |
| *valueOut = &iter->second.value; |
| return true; |
| } |
| |
| bool getAt(size_t index, const Key **keyOut, const Value **valueOut) |
| { |
| if (index < mStore.size()) |
| { |
| auto it = mStore.begin(); |
| std::advance(it, index); |
| *keyOut = &it->first; |
| *valueOut = &it->second.value; |
| return true; |
| } |
| *valueOut = nullptr; |
| return false; |
| } |
| |
| bool empty() const { return mStore.empty(); } |
| |
| void clear() |
| { |
| mStore.Clear(); |
| mCurrentSize = 0; |
| } |
| |
| void eraseByKey(const Key &key) |
| { |
| // Check for existing key. |
| auto existing = mStore.Peek(key); |
| if (existing != mStore.end()) |
| { |
| mCurrentSize -= existing->second.size; |
| mStore.Erase(existing); |
| } |
| } |
| |
| size_t entryCount() const { return mStore.size(); } |
| |
| size_t size() const { return mCurrentSize; } |
| |
| // Also discards the cache contents. |
| void resize(size_t maximumTotalSize) |
| { |
| clear(); |
| mMaximumTotalSize = maximumTotalSize; |
| } |
| |
| // Reduce current memory usage. |
| size_t shrinkToSize(size_t limit) |
| { |
| size_t initialSize = mCurrentSize; |
| |
| while (mCurrentSize > limit) |
| { |
| ASSERT(!mStore.empty()); |
| auto iter = mStore.rbegin(); |
| mCurrentSize -= iter->second.size; |
| mStore.Erase(iter); |
| } |
| |
| return (initialSize - mCurrentSize); |
| } |
| |
| size_t maxSize() const { return mMaximumTotalSize; } |
| |
| private: |
| struct ValueAndSize |
| { |
| ValueAndSize() : value(), size(0) {} |
| ValueAndSize(Value &&value, size_t size) : value(std::move(value)), size(size) {} |
| ValueAndSize(ValueAndSize &&other) : ValueAndSize() { *this = std::move(other); } |
| ValueAndSize &operator=(ValueAndSize &&other) |
| { |
| std::swap(value, other.value); |
| std::swap(size, other.size); |
| return *this; |
| } |
| |
| Value value; |
| size_t size; |
| }; |
| |
| using SizedMRUCacheStore = base::HashingMRUCache<Key, ValueAndSize>; |
| |
| size_t mMaximumTotalSize; |
| size_t mCurrentSize; |
| SizedMRUCacheStore mStore; |
| }; |
| |
| // Helper function used in a few places. |
| template <typename T> |
| void TrimCache(size_t maxStates, size_t gcLimit, const char *name, T *cache) |
| { |
| const size_t kGarbageCollectionLimit = maxStates / 2 + gcLimit; |
| |
| if (cache->size() >= kGarbageCollectionLimit) |
| { |
| WARN() << "Overflowed the " << name << " cache limit of " << (maxStates / 2) |
| << " elements, removing the least recently used to make room."; |
| cache->ShrinkToSize(maxStates / 2); |
| } |
| } |
| } // namespace angle |
| #endif // LIBANGLE_SIZED_MRU_CACHE_H_ |