1 #ifndef DALI_TEXT_ABSTRACTION_INTERNAL_LRU_CACHE_CONTAINER_H
2 #define DALI_TEXT_ABSTRACTION_INTERNAL_LRU_CACHE_CONTAINER_H
5 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
21 #include <dali/public-api/common/dali-common.h>
22 #include <dali/public-api/common/vector-wrapper.h>
23 #include <limits> // for std::numeric_limits
24 #include <unordered_map>
26 namespace Dali::TextAbstraction::Internal
29 * @brief Helper class to cache as LRU algirhtm.
30 * It implement as double-linked-list with header and footer.
32 * HEADER <-> n(LatestId) <-> n <-> ... <-> n(OldestId) <-> FOOTER <-> n(FreeId) <-> n <-> .. <-> n <-> HEADER
34 * @note This container cannot control the KeyType and ElementType construct and destruct timming.
36 * @todo Could we make "iteration" system here?
37 * @todo Could we move it into dali-core/devel-api?
39 * @tparam KeyType The type of the key that pairwise with element.
40 * @tparam ElementType The type of the data that the container holds
41 * @tparam KeyHash The custom hash funcion of KeyType. Default is std::hash
42 * @tparam KeyEqual The custom equal function of KeyType. Default is std::equal_to
44 template<class KeyType, class ElementType, class KeyHash = std::hash<KeyType>, class KeyEqual = std::equal_to<KeyType>>
45 class LRUCacheContainer
49 LRUCacheContainer(std::size_t maxNumberOfCache = std::numeric_limits<std::size_t>::max() - 2u)
50 : mCacheMaxSize(maxNumberOfCache)
55 ~LRUCacheContainer() = default;
57 LRUCacheContainer(const LRUCacheContainer& rhs) = default;
58 LRUCacheContainer(LRUCacheContainer&& rhs) = default;
60 LRUCacheContainer& operator=(const LRUCacheContainer& rhs) = default;
61 LRUCacheContainer& operator=(LRUCacheContainer&& rhs) = default;
64 // Public struct area.
65 using CacheId = std::size_t; ///< The id of cached element. It can be used until element poped out.
66 using CacheIdContainer = std::unordered_map<KeyType, CacheId, KeyHash, KeyEqual>;
69 * @brief Special CacheId for header.
71 static constexpr CacheId CACHE_HEADER_ID = std::numeric_limits<std::size_t>::max();
73 * @brief Special CacheId for footer.
75 static constexpr CacheId CACHE_FOOTER_ID = std::numeric_limits<std::size_t>::max() - 1u;
80 * @brief Iterator of this LRU cache.
82 * Add, Get and Clear invalidate the itertator's validation.
83 * Erase and Pop validate the iterator.
85 * Range based iteration doesn't supported.
90 iterator(LRUCacheContainer& owner, const CacheId& id)
96 // copy constructor & assign
97 iterator(const iterator& rhs)
103 iterator& operator=(const iterator& rhs)
105 this->owner = rhs.owner;
110 // Move constructor & assign
111 iterator(iterator&& rhs)
112 : owner(std::move(rhs.owner)),
117 iterator& operator=(iterator&& rhs)
119 this->owner = std::move(rhs.owner);
125 iterator& operator++()
127 id = owner.mData[id].next;
132 iterator operator++(int)
134 iterator temp = *this;
140 iterator& operator--()
142 id = owner.mData[id].prev;
147 iterator operator--(int)
149 iterator temp = *this;
154 bool operator==(const iterator& rhs)
156 return id == rhs.id && (&owner) == (&rhs.owner);
158 bool operator!=(const iterator& rhs)
160 return id != rhs.id || (&owner) != (&rhs.owner);
164 LRUCacheContainer& owner; // The reference of owner of this iterator.
172 * @brief Push an element into the cache. It will be marked as recent
173 * If it is already existed key, it will replace element.
175 * @param[in] key The key to push
176 * @param[in] element The element to push
177 * @warning This method pop oldest elements if the user attempts to push
178 * more elements than the maximum size specified in the constructor
180 void Push(const KeyType& key, const ElementType& element)
182 const auto iter = mCacheId.find(key);
184 // If already exist key, just replace element, and return.
185 if(iter != mCacheId.end())
187 const CacheId id = iter->second;
189 // Mark as recently used.
191 InternalInsertAfterHeader(id);
193 mData[id].element = element;
197 if(DALI_UNLIKELY(IsFull()))
199 // Pop latest element automatically.
203 if(DALI_UNLIKELY(mNumberOfElements == mData.size()))
205 InternalReserve(mNumberOfElements == 0 ? 1 : (mNumberOfElements << 1));
210 const CacheId id = mFreeId;
212 // Mark as recently used.
214 InternalInsertAfterHeader(id);
217 mData[id].element = element;
219 // Store cache iterator.
220 mData[id].cacheIdIterator = mCacheId.emplace(key, id).first;
224 * @brief Pops an element off the oldest used element.
225 * After pop, CacheId relative with this element cannot be used.
226 * Access by poped element's CacheId is Undefined Behavior.
228 * @return A copy of the element
229 * @warning This method asserts if the container is empty
233 DALI_ASSERT_ALWAYS(!IsEmpty() && "Reading from empty container");
235 const CacheId id = mOldestId;
237 InternalInsertAfterFooter(id);
242 mCacheId.erase(mData[id].cacheIdIterator);
244 return mData[id].element;
248 * @brief Get an element by the key. It will be marked as recent
250 * @param[in] key The key of element
251 * @return A reference of the element
252 * @warning This method asserts if invalid key inputed
254 ElementType& Get(const KeyType& key)
256 const auto iter = mCacheId.find(key);
257 DALI_ASSERT_ALWAYS((iter != mCacheId.end()) && "Try to get invalid key");
259 const auto id = iter->second;
261 // Mark as recently used.
263 InternalInsertAfterHeader(id);
265 return mData[id].element;
269 * @brief Get an key by iterator. It will not be marked as recent
271 * @param[in] iter The iterator of element
272 * @return A reference of the key
273 * @warning This method don't check iterator validation
275 const KeyType& GetKey(iterator iter)
277 const auto id = iter.id;
279 return mData[id].cacheIdIterator->first;
283 * @brief Get an element by iterator. It will not be marked as recent
285 * @param[in] iter The iterator of element
286 * @return A reference of the element
287 * @warning This method don't check iterator validation
289 ElementType& GetElement(iterator iter)
291 const auto id = iter.id;
293 return mData[id].element;
297 * @brief Find an element by the key. It will not be marked as recent
299 * @param[in] key The key of element
300 * @return A iterator of cache node. If key not exist, return End()
302 iterator Find(const KeyType& key)
304 if(mCacheId.find(key) == mCacheId.end())
309 const auto id = mCacheId[key];
311 return iterator(*this, id);
315 * @brief Clear all data
322 mData.shrink_to_fit();
324 mNumberOfElements = 0;
325 mLatestId = CACHE_FOOTER_ID;
326 mOldestId = CACHE_HEADER_ID;
327 mFreeId = CACHE_HEADER_ID;
331 * @brief Predicate to determine if the container is empty
333 * @return true if the container is empty
337 return mNumberOfElements == 0;
341 * @brief Predicate to determine if the container is full
343 * @return true if the container is full
347 return (mNumberOfElements == mCacheMaxSize);
352 return iterator(*this, mLatestId);
357 return iterator(*this, CACHE_FOOTER_ID);
361 * @brief Remove cache item by iterator.
363 * @param[in] iter The iterator what we want to remove.
364 * @return iterator The next iterator after remove
365 * @warning This method don't check iterator validation
367 iterator Erase(iterator iter)
369 const auto id = iter.id;
370 const auto nextId = mData[id].next;
372 InternalInsertAfterFooter(id);
377 mCacheId.erase(mData[id].cacheIdIterator);
379 return iterator(*this, nextId);
383 * @brief Get a count of the elements in the container
385 * @return the number of elements in the container.
387 std::size_t Count() const
389 return mNumberOfElements;
396 * @brief Allocate cache memory as reserveSize.
397 * @note We assume that mFreeId is header.
399 * @param reserveSize Reserved size of cache.
401 void InternalReserve(std::size_t reserveSize) noexcept
403 // Increase mData capacity
404 if(reserveSize > mCacheMaxSize)
406 reserveSize = mCacheMaxSize;
409 CacheId newCreatedIdBegin = mData.size();
410 CacheId newCreatedIdEnd = reserveSize - 1;
412 // Make temporary array for re-validate iterator.
413 std::vector<KeyType> keyList(mData.size());
414 for(auto i = static_cast<std::size_t>(0); i < newCreatedIdBegin; ++i)
416 keyList[i] = mData[i].cacheIdIterator->first;
419 // Reserve data and cacheid capacity.
420 mData.resize(reserveSize);
421 mCacheId.rehash(reserveSize);
423 // Revalidate each iterator.
424 for(auto i = static_cast<std::size_t>(0); i < newCreatedIdBegin; ++i)
426 mData[i].cacheIdIterator = mCacheId.find(keyList[i]);
429 // Setup new created CacheNode's prev and next id.
430 for(auto i = newCreatedIdBegin;; ++i)
432 mData[i].prev = DALI_UNLIKELY(i == newCreatedIdBegin) ? CACHE_FOOTER_ID : i - 1;
433 mData[i].next = DALI_UNLIKELY(i == newCreatedIdEnd) ? CACHE_HEADER_ID : i + 1;
434 if(DALI_UNLIKELY(i == newCreatedIdEnd))
439 mFreeId = newCreatedIdBegin;
443 * @brief Temperary pop of node. After call this, we should call
444 * InternalInsertAfterHeader or InternalInsertAfterFooter
446 * @param id CacheId that removed temperary.
448 void InternalPop(const CacheId& id) noexcept
450 const CacheId prev = mData[id].prev;
451 const CacheId next = mData[id].next;
453 // Disconnect prev -> id. and connect prev -> next
454 if(prev == CACHE_HEADER_ID)
458 else if(prev == CACHE_FOOTER_ID)
464 mData[prev].next = next;
467 // Disconnect id <- next. and connect prev <- next
468 if(next == CACHE_HEADER_ID)
472 else if(next == CACHE_FOOTER_ID)
478 mData[next].prev = prev;
483 * @brief Insert the node after the header. That mean, this id recently used.
485 * @param id CacheId that insert after header.
487 void InternalInsertAfterHeader(const CacheId& id) noexcept
489 const CacheId next = mLatestId;
491 // Connect Header -> id.
494 // Connect id <- next
495 if(next == CACHE_FOOTER_ID)
501 mData[next].prev = id;
504 // Connect Header <- id -> next
505 mData[id].prev = CACHE_HEADER_ID;
506 mData[id].next = next;
510 * @brief Insert the node after the footer. That mean, this id become free.
512 * @param id CacheId that insert after footer.
514 void InternalInsertAfterFooter(const CacheId& id) noexcept
516 const CacheId next = mFreeId;
518 // Connect Footer -> id.
521 // Connect id <- next
522 if(next == CACHE_HEADER_ID)
528 mData[next].prev = id;
531 // Connect Footer <- id -> next
532 mData[id].prev = CACHE_FOOTER_ID;
533 mData[id].next = next;
537 // Private struct area.
539 * @brief Double linked CacheNode that this container used.
543 CacheNode() = default;
544 ~CacheNode() = default;
546 CacheId prev{CACHE_FOOTER_ID};
547 CacheId next{CACHE_HEADER_ID};
550 using CacheIdIterator = typename CacheIdContainer::iterator;
552 CacheIdIterator cacheIdIterator; ///< Note : It only validate until mCacheId rehashing.
556 // Private member value area.
557 std::size_t mCacheMaxSize{0}; ///< The maximum capacity of cache.
558 std::size_t mNumberOfElements{0}; ///< The number of elements.
560 CacheId mLatestId{CACHE_FOOTER_ID}; ///< The recently used element id
561 CacheId mOldestId{CACHE_HEADER_ID}; ///< The oldest used element id
562 CacheId mFreeId{CACHE_HEADER_ID}; ///< The free element id that can be allocated.
564 std::unordered_map<KeyType, CacheId, KeyHash, KeyEqual> mCacheId{}; ///< LRU Cache id container
565 std::vector<CacheNode> mData{}; ///< The real data container.
568 } // namespace Dali::TextAbstraction::Internal
570 #endif //DALI_TEXT_ABSTRACTION_INTERNAL_LRU_CACHE_CONTAINER_H