| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef ANDROID_BASE_MRUCACHE_ |
| #define ANDROID_BASE_MRUCACHE_ |
| |
| #include <algorithm> |
| #include <list> |
| #include <map> |
| |
| namespace android { |
| namespace base { |
| |
| // A trivial MRU cache. Not thread-safe. |
| template <class K, class V> |
| class MruCache { |
| public: |
| template <class S> |
| struct EntryWithSize { |
| EntryWithSize(const S&& d) : |
| EntryWithSize(std::move(d), 0) {} |
| |
| EntryWithSize(const S&& d, const size_t ds) : |
| data(std::move(d)), |
| dataSize(ds) { |
| static_assert(std::is_same<S, K>::value || |
| std::is_same<S, V>::value, "Cache entry instantiated with invalid types"); |
| } |
| |
| const S data; |
| size_t dataSize; |
| |
| bool operator==(const EntryWithSize& rhs) const { return data == rhs.data; } |
| bool operator<(const EntryWithSize& rhs) const { return data < rhs.data; } |
| |
| }; |
| |
| class MruCacheObserver { |
| public: |
| virtual void cacheChanged() = 0; |
| virtual ~MruCacheObserver() {} |
| }; |
| |
| class CacheFlattener { |
| public: |
| virtual void handleFlatten(std::map<EntryWithSize<K>, EntryWithSize<V>>& mCache, void* buf, size_t bufSize) = 0; |
| virtual ~CacheFlattener() {} |
| }; |
| |
| MruCache(size_t maxEntries, CacheFlattener* cacheFlattener) : |
| mMaxEntries(maxEntries), |
| mCacheFlattener(cacheFlattener) {} |
| |
| bool put(const K& key, size_t keySize, V&& value, size_t valueSize) { |
| evictIfNecessary(); |
| |
| EntryWithSize<K> cacheKey(std::move(key), keySize); |
| EntryWithSize<V> cacheValue(std::move(value), valueSize); |
| |
| auto exists = mCache.find(cacheKey); |
| if (exists != mCache.end()) { |
| auto iter = std::find(mMostRecents.begin(), mMostRecents.end(), cacheKey); |
| mMostRecents.splice(mMostRecents.begin(), mMostRecents, iter); |
| mCache.erase(exists); |
| } |
| else { |
| mMostRecents.push_front(cacheKey); |
| } |
| |
| const auto [_, res] = mCache.insert({ cacheKey, std::move(cacheValue) }); |
| |
| if (mCacheObserver != nullptr && res) { |
| mCacheObserver->cacheChanged(); |
| } |
| |
| return res; |
| } |
| |
| void flatten(void* buf, size_t bufSize) { |
| if (mCacheFlattener) { |
| mCacheFlattener->handleFlatten(mCache, buf, bufSize); |
| } |
| } |
| |
| bool get(const K& key, const V** value) { |
| EntryWithSize<K> cacheKey(std::move(key)); |
| auto res = mCache.find(cacheKey); |
| |
| if (res == mCache.end()) { |
| return false; |
| } |
| |
| *value = &(res->second.data); |
| auto iter = std::find(mMostRecents.begin(), mMostRecents.end(), cacheKey); |
| mMostRecents.splice(mMostRecents.begin(), mMostRecents, iter); |
| |
| return true; |
| } |
| |
| void setObserver(MruCacheObserver* observer) { |
| mCacheObserver = observer; |
| } |
| |
| private: |
| using MruCacheMap = std::map<EntryWithSize<K>, EntryWithSize<V>>; |
| using MostRecentList = std::list<EntryWithSize<K>>; |
| |
| void evictIfNecessary() { |
| auto entryCount = mMostRecents.size(); |
| if (entryCount >= mMaxEntries) { |
| auto threshold = entryCount * 0.9; |
| |
| for (int i = mMostRecents.size(); i > threshold; i--) { |
| EntryWithSize<K> key = mMostRecents.front(); |
| mMostRecents.pop_front(); |
| mCache.erase(key); |
| } |
| } |
| } |
| |
| MruCacheMap mCache; |
| MostRecentList mMostRecents; |
| MruCacheObserver* mCacheObserver; |
| CacheFlattener* mCacheFlattener; |
| const size_t mMaxEntries; |
| }; |
| |
| } |
| } |
| |
| #endif |