|  | // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | // This file contains a template for a Most Recently Used cache that allows | 
|  | // constant-time access to items using a key, but easy identification of the | 
|  | // least-recently-used items for removal.  Each key can only be associated with | 
|  | // one payload item at a time. | 
|  | // | 
|  | // The key object will be stored twice, so it should support efficient copying. | 
|  | // | 
|  | // NOTE: While all operations are O(1), this code is written for | 
|  | // legibility rather than optimality. If future profiling identifies this as | 
|  | // a bottleneck, there is room for smaller values of 1 in the O(1). :] | 
|  |  | 
|  | #ifndef BASE_CONTAINERS_MRU_CACHE_H_ | 
|  | #define BASE_CONTAINERS_MRU_CACHE_H_ | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <functional> | 
|  | #include <list> | 
|  | #include <map> | 
|  | #include <unordered_map> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "starboard/types.h" | 
|  |  | 
|  | namespace base { | 
|  | namespace trace_event { | 
|  | namespace internal { | 
|  |  | 
|  | template <class MruCacheType> | 
|  | size_t DoEstimateMemoryUsageForMruCache(const MruCacheType&); | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace trace_event | 
|  |  | 
|  | // MRUCacheBase ---------------------------------------------------------------- | 
|  |  | 
|  | // This template is used to standardize map type containers that can be used | 
|  | // by MRUCacheBase. This level of indirection is necessary because of the way | 
|  | // that template template params and default template params interact. | 
|  | template <class KeyType, class ValueType, class CompareType> | 
|  | struct MRUCacheStandardMap { | 
|  | typedef std::map<KeyType, ValueType, CompareType> Type; | 
|  | }; | 
|  |  | 
|  | // Base class for the MRU cache specializations defined below. | 
|  | template <class KeyType, | 
|  | class PayloadType, | 
|  | class HashOrCompareType, | 
|  | template <typename, typename, typename> class MapType = | 
|  | MRUCacheStandardMap> | 
|  | class MRUCacheBase { | 
|  | public: | 
|  | // The payload of the list. This maintains a copy of the key so we can | 
|  | // efficiently delete things given an element of the list. | 
|  | typedef std::pair<KeyType, PayloadType> value_type; | 
|  |  | 
|  | private: | 
|  | typedef std::list<value_type> PayloadList; | 
|  | typedef typename MapType<KeyType, | 
|  | typename PayloadList::iterator, | 
|  | HashOrCompareType>::Type KeyIndex; | 
|  |  | 
|  | public: | 
|  | typedef typename PayloadList::size_type size_type; | 
|  |  | 
|  | typedef typename PayloadList::iterator iterator; | 
|  | typedef typename PayloadList::const_iterator const_iterator; | 
|  | typedef typename PayloadList::reverse_iterator reverse_iterator; | 
|  | typedef typename PayloadList::const_reverse_iterator const_reverse_iterator; | 
|  |  | 
|  | enum { NO_AUTO_EVICT = 0 }; | 
|  |  | 
|  | // The max_size is the size at which the cache will prune its members to when | 
|  | // a new item is inserted. If the caller wants to manager this itself (for | 
|  | // example, maybe it has special work to do when something is evicted), it | 
|  | // can pass NO_AUTO_EVICT to not restrict the cache size. | 
|  | explicit MRUCacheBase(size_type max_size) : max_size_(max_size) {} | 
|  |  | 
|  | virtual ~MRUCacheBase() = default; | 
|  |  | 
|  | size_type max_size() const { return max_size_; } | 
|  |  | 
|  | // Inserts a payload item with the given key. If an existing item has | 
|  | // the same key, it is removed prior to insertion. An iterator indicating the | 
|  | // inserted item will be returned (this will always be the front of the list). | 
|  | // | 
|  | // The payload will be forwarded. | 
|  | template <typename Payload> | 
|  | iterator Put(const KeyType& key, Payload&& payload) { | 
|  | // Remove any existing payload with that key. | 
|  | typename KeyIndex::iterator index_iter = index_.find(key); | 
|  | if (index_iter != index_.end()) { | 
|  | // Erase the reference to it. The index reference will be replaced in the | 
|  | // code below. | 
|  | Erase(index_iter->second); | 
|  | } else if (max_size_ != NO_AUTO_EVICT) { | 
|  | // New item is being inserted which might make it larger than the maximum | 
|  | // size: kick the oldest thing out if necessary. | 
|  | ShrinkToSize(max_size_ - 1); | 
|  | } | 
|  |  | 
|  | ordering_.emplace_front(key, std::forward<Payload>(payload)); | 
|  | index_.emplace(key, ordering_.begin()); | 
|  | return ordering_.begin(); | 
|  | } | 
|  |  | 
|  | // Retrieves the contents of the given key, or end() if not found. This method | 
|  | // has the side effect of moving the requested item to the front of the | 
|  | // recency list. | 
|  | iterator Get(const KeyType& key) { | 
|  | typename KeyIndex::iterator index_iter = index_.find(key); | 
|  | if (index_iter == index_.end()) | 
|  | return end(); | 
|  | typename PayloadList::iterator iter = index_iter->second; | 
|  |  | 
|  | // Move the touched item to the front of the recency ordering. | 
|  | ordering_.splice(ordering_.begin(), ordering_, iter); | 
|  | return ordering_.begin(); | 
|  | } | 
|  |  | 
|  | // Retrieves the payload associated with a given key and returns it via | 
|  | // result without affecting the ordering (unlike Get). | 
|  | iterator Peek(const KeyType& key) { | 
|  | typename KeyIndex::const_iterator index_iter = index_.find(key); | 
|  | if (index_iter == index_.end()) | 
|  | return end(); | 
|  | return index_iter->second; | 
|  | } | 
|  |  | 
|  | const_iterator Peek(const KeyType& key) const { | 
|  | typename KeyIndex::const_iterator index_iter = index_.find(key); | 
|  | if (index_iter == index_.end()) | 
|  | return end(); | 
|  | return index_iter->second; | 
|  | } | 
|  |  | 
|  | // Exchanges the contents of |this| by the contents of the |other|. | 
|  | void Swap(MRUCacheBase& other) { | 
|  | ordering_.swap(other.ordering_); | 
|  | index_.swap(other.index_); | 
|  | std::swap(max_size_, other.max_size_); | 
|  | } | 
|  |  | 
|  | // Erases the item referenced by the given iterator. An iterator to the item | 
|  | // following it will be returned. The iterator must be valid. | 
|  | iterator Erase(iterator pos) { | 
|  | index_.erase(pos->first); | 
|  | return ordering_.erase(pos); | 
|  | } | 
|  |  | 
|  | // MRUCache entries are often processed in reverse order, so we add this | 
|  | // convenience function (not typically defined by STL containers). | 
|  | reverse_iterator Erase(reverse_iterator pos) { | 
|  | // We have to actually give it the incremented iterator to delete, since | 
|  | // the forward iterator that base() returns is actually one past the item | 
|  | // being iterated over. | 
|  | return reverse_iterator(Erase((++pos).base())); | 
|  | } | 
|  |  | 
|  | // Shrinks the cache so it only holds |new_size| items. If |new_size| is | 
|  | // bigger or equal to the current number of items, this will do nothing. | 
|  | void ShrinkToSize(size_type new_size) { | 
|  | for (size_type i = size(); i > new_size; i--) | 
|  | Erase(rbegin()); | 
|  | } | 
|  |  | 
|  | // Deletes everything from the cache. | 
|  | void Clear() { | 
|  | index_.clear(); | 
|  | ordering_.clear(); | 
|  | } | 
|  |  | 
|  | // Returns the number of elements in the cache. | 
|  | size_type size() const { | 
|  | // We don't use ordering_.size() for the return value because | 
|  | // (as a linked list) it can be O(n). | 
|  | DCHECK(index_.size() == ordering_.size()); | 
|  | return index_.size(); | 
|  | } | 
|  |  | 
|  | // Allows iteration over the list. Forward iteration starts with the most | 
|  | // recent item and works backwards. | 
|  | // | 
|  | // Note that since these iterators are actually iterators over a list, you | 
|  | // can keep them as you insert or delete things (as long as you don't delete | 
|  | // the one you are pointing to) and they will still be valid. | 
|  | iterator begin() { return ordering_.begin(); } | 
|  | const_iterator begin() const { return ordering_.begin(); } | 
|  | iterator end() { return ordering_.end(); } | 
|  | const_iterator end() const { return ordering_.end(); } | 
|  |  | 
|  | reverse_iterator rbegin() { return ordering_.rbegin(); } | 
|  | const_reverse_iterator rbegin() const { return ordering_.rbegin(); } | 
|  | reverse_iterator rend() { return ordering_.rend(); } | 
|  | const_reverse_iterator rend() const { return ordering_.rend(); } | 
|  |  | 
|  | bool empty() const { return ordering_.empty(); } | 
|  |  | 
|  | private: | 
|  | template <class MruCacheType> | 
|  | friend size_t trace_event::internal::DoEstimateMemoryUsageForMruCache( | 
|  | const MruCacheType&); | 
|  |  | 
|  | PayloadList ordering_; | 
|  | KeyIndex index_; | 
|  |  | 
|  | size_type max_size_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(MRUCacheBase); | 
|  | }; | 
|  |  | 
|  | // MRUCache -------------------------------------------------------------------- | 
|  |  | 
|  | // A container that does not do anything to free its data. Use this when storing | 
|  | // value types (as opposed to pointers) in the list. | 
|  | template <class KeyType, | 
|  | class PayloadType, | 
|  | class CompareType = std::less<KeyType>> | 
|  | class MRUCache : public MRUCacheBase<KeyType, PayloadType, CompareType> { | 
|  | private: | 
|  | using ParentType = MRUCacheBase<KeyType, PayloadType, CompareType>; | 
|  |  | 
|  | public: | 
|  | // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. | 
|  | explicit MRUCache(typename ParentType::size_type max_size) | 
|  | : ParentType(max_size) {} | 
|  | virtual ~MRUCache() = default; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(MRUCache); | 
|  | }; | 
|  |  | 
|  | // HashingMRUCache ------------------------------------------------------------ | 
|  |  | 
|  | template <class KeyType, class ValueType, class HashType> | 
|  | struct MRUCacheHashMap { | 
|  | typedef std::unordered_map<KeyType, ValueType, HashType> Type; | 
|  | }; | 
|  |  | 
|  | // This class is similar to MRUCache, except that it uses std::unordered_map as | 
|  | // the map type instead of std::map. Note that your KeyType must be hashable to | 
|  | // use this cache or you need to provide a hashing class. | 
|  | template <class KeyType, class PayloadType, class HashType = std::hash<KeyType>> | 
|  | class HashingMRUCache | 
|  | : public MRUCacheBase<KeyType, PayloadType, HashType, MRUCacheHashMap> { | 
|  | private: | 
|  | using ParentType = | 
|  | MRUCacheBase<KeyType, PayloadType, HashType, MRUCacheHashMap>; | 
|  |  | 
|  | public: | 
|  | // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT. | 
|  | explicit HashingMRUCache(typename ParentType::size_type max_size) | 
|  | : ParentType(max_size) {} | 
|  | virtual ~HashingMRUCache() = default; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(HashingMRUCache); | 
|  | }; | 
|  |  | 
|  | }  // namespace base | 
|  |  | 
|  | #endif  // BASE_CONTAINERS_MRU_CACHE_H_ |