LruList.h 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #ifndef GRAPHICS_LRULIST_H
  2. #define GRAPHICS_LRULIST_H
  3. #include <cstdlib>
  4. #include <vector>
  5. /*
  6. Implementation details:
  7. - The first entry has the previous index pointing to itself to simplify eviction of the last entry.
  8. - Invalidation is simply done by incrementing the entry's epoch. This is trivially safe.
  9. */
  10. template<typename T>
  11. class LruList final {
  12. private:
  13. struct Entry {
  14. unsigned previousIndex, nextIndex;
  15. unsigned epoch, lastUsed;
  16. T data;
  17. };
  18. std::vector<Entry> pool{1 << 10};
  19. std::size_t size = 0;
  20. unsigned headIndex = 0, tailIndex = 0;
  21. unsigned currentEpoch = 0;
  22. public:
  23. struct Handle {
  24. unsigned index;
  25. unsigned epoch;
  26. };
  27. LruList();
  28. void tick();
  29. Handle add(T data);
  30. bool isAlive(Handle handle);
  31. bool ping(Handle handle);
  32. void remove(Handle handle);
  33. void evictLast();
  34. T* getData(Handle handle);
  35. const T* getData(Handle handle) const;
  36. T* getLast();
  37. const T* getLast() const;
  38. unsigned getLastEntryAge() const;
  39. };
  40. template<typename T>
  41. LruList<T>::LruList() {
  42. const unsigned poolSize = static_cast<unsigned>(pool.size());
  43. for (unsigned i = 0; i != poolSize; ++i) pool[i].nextIndex = i + 1;
  44. }
  45. template<typename T>
  46. void LruList<T>::tick() {
  47. ++currentEpoch;
  48. }
  49. template<typename T>
  50. typename LruList<T>::Handle LruList<T>::add(const T data) {
  51. unsigned poolSize = static_cast<unsigned>(pool.size());
  52. if (size == poolSize) {
  53. poolSize <<= 1;
  54. pool.resize(poolSize);
  55. for (unsigned i = static_cast<unsigned>(size); i != poolSize; ++i) pool[i].nextIndex = i + 1;
  56. pool[tailIndex].nextIndex = static_cast<unsigned>(size);
  57. }
  58. if (size == 0) {
  59. Entry &newEntry = pool[headIndex];
  60. newEntry.previousIndex = headIndex;
  61. newEntry.epoch = currentEpoch;
  62. newEntry.lastUsed = currentEpoch;
  63. newEntry.data = data;
  64. size = 1;
  65. // No need to update head and tail indices here, they are already correct.
  66. return {headIndex, currentEpoch};
  67. }
  68. Entry &tail = pool[tailIndex];
  69. const unsigned newIndex = tail.nextIndex;
  70. Entry &newEntry = pool[newIndex];
  71. tail.nextIndex = newEntry.nextIndex;
  72. newEntry = {newIndex, headIndex, currentEpoch, currentEpoch, data};
  73. pool[headIndex].previousIndex = newIndex;
  74. headIndex = newIndex;
  75. ++size;
  76. return {newIndex, currentEpoch};
  77. }
  78. template<typename T>
  79. bool LruList<T>::isAlive(const Handle handle) {
  80. return pool[handle.index] == handle.epoch;
  81. }
  82. template<typename T>
  83. bool LruList<T>::ping(const Handle handle) {
  84. Entry &entry = pool[handle.index];
  85. if (entry.epoch != handle.epoch) return false;
  86. if (entry.lastUsed == currentEpoch) return true;
  87. entry.lastUsed = currentEpoch;
  88. if (handle.index == headIndex) return true;
  89. pool[entry.previousIndex].nextIndex = entry.nextIndex;
  90. if (handle.index == tailIndex) tailIndex = entry.previousIndex;
  91. else pool[entry.nextIndex].previousIndex = entry.previousIndex;
  92. pool[headIndex].previousIndex = handle.index;
  93. entry.previousIndex = handle.index;
  94. entry.nextIndex = headIndex;
  95. headIndex = handle.index;
  96. return true;
  97. }
  98. template<typename T>
  99. void LruList<T>::remove(const Handle handle) {
  100. Entry &entry = pool[handle.index];
  101. if (entry.epoch != handle.epoch) return;
  102. ++entry.epoch;
  103. if (size == 1) {
  104. size = 0;
  105. return;
  106. }
  107. if (handle.index == headIndex) {
  108. Entry &tail = pool[tailIndex];
  109. entry.nextIndex = tail.nextIndex;
  110. tail.nextIndex = headIndex;
  111. headIndex = entry.nextIndex;
  112. } else if (handle.index == tailIndex) {
  113. tailIndex = entry.previousIndex;
  114. } else {
  115. pool[entry.previousIndex].nextIndex = entry.nextIndex;
  116. pool[entry.nextIndex].previousIndex = entry.previousIndex;
  117. Entry &tail = pool[tailIndex];
  118. entry.nextIndex = tail.nextIndex;
  119. tail.nextIndex = handle.index;
  120. }
  121. --size;
  122. }
  123. template<typename T>
  124. void LruList<T>::evictLast() {
  125. if (size == 0) return;
  126. Entry &entry = pool[tailIndex];
  127. ++entry.epoch;
  128. tailIndex = entry.previousIndex;
  129. --size;
  130. }
  131. template<typename T>
  132. T* LruList<T>::getData(const Handle handle) {
  133. Entry &entry = pool[handle.index];
  134. return entry.epoch == handle.epoch ? &entry.data : nullptr;
  135. }
  136. template<typename T>
  137. const T* LruList<T>::getData(const Handle handle) const {
  138. Entry &entry = pool[handle.index];
  139. return entry.epoch == handle.epoch ? &entry.data : nullptr;
  140. }
  141. template<typename T>
  142. T* LruList<T>::getLast() {
  143. return size == 0 ? nullptr : &pool[tailIndex].data;
  144. }
  145. template<typename T>
  146. const T* LruList<T>::getLast() const {
  147. return size == 0 ? nullptr : &pool[tailIndex].data;
  148. }
  149. template<typename T>
  150. unsigned LruList<T>::getLastEntryAge() const {
  151. return size == 0 ? 0 : currentEpoch - pool[tailIndex].lastUsed;
  152. }
  153. #endif // GRAPHICS_LRULIST_H