blob: b557e657ec5c2fd504bd025eff1227cfc0634199 [file] [log] [blame]
//
// 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_