From e540ab70385a89b9e5737eac927aa354478766a0 Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Sun, 21 Feb 2021 03:45:50 +1000
Subject: [PATCH] Common: Add LRUCache class

---
 src/common/common.vcxproj         |   1 +
 src/common/common.vcxproj.filters |   1 +
 src/common/lru_cache.h            | 101 ++++++++++++++++++++++++++++++
 3 files changed, 103 insertions(+)
 create mode 100644 src/common/lru_cache.h

diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index 7d405fcdc..3f281daef 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -87,6 +87,7 @@
     <ClInclude Include="iso_reader.h" />
     <ClInclude Include="jit_code_buffer.h" />
     <ClInclude Include="log.h" />
+    <ClInclude Include="lru_cache.h" />
     <ClInclude Include="make_array.h" />
     <ClInclude Include="md5_digest.h" />
     <ClInclude Include="null_audio_stream.h" />
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index 61d7a5c94..5dd50afb0 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -108,6 +108,7 @@
       <Filter>thirdparty</Filter>
     </ClInclude>
     <ClInclude Include="crash_handler.h" />
+    <ClInclude Include="lru_cache.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="jit_code_buffer.cpp" />
diff --git a/src/common/lru_cache.h b/src/common/lru_cache.h
new file mode 100644
index 000000000..e6f8bcc4b
--- /dev/null
+++ b/src/common/lru_cache.h
@@ -0,0 +1,101 @@
+#pragma once
+#include <cstdint>
+#include <map>
+
+template<class K, class V>
+class LRUCache
+{
+  using CounterType = std::uint64_t;
+
+  struct Item
+  {
+    V value;
+    CounterType last_access;
+  };
+
+  using MapType = std::map<K, Item>;
+
+public:
+  LRUCache(std::size_t max_capacity = 16) : m_max_capacity(max_capacity) {}
+  ~LRUCache() = default;
+
+  std::size_t GetSize() const { return m_items.size(); }
+  std::size_t GetMaxCapacity() const { return m_max_capacity; }
+
+  void Clear() { m_items.clear(); }
+
+  void SetMaxCapacity(std::size_t capacity)
+  {
+    m_max_capacity = capacity;
+    if (m_items.size() > m_max_capacity)
+      Evict(m_items.size() - m_max_capacity);
+  }
+
+  V* Lookup(const K& key)
+  {
+    auto iter = m_items.find(key);
+    if (iter == m_items.end())
+      return nullptr;
+
+    iter->second.last_access = ++m_last_counter;
+    return &iter->second.value;
+  }
+
+  V* Insert(const K& key, V value)
+  {
+    ShrinkForNewItem();
+
+    auto iter = m_items.find(key);
+    if (iter != m_items.end())
+    {
+      iter->second.value = std::move(value);
+      iter->second.last_access = ++m_last_counter;
+      return &iter->second.value;
+    }
+    else
+    {
+      Item it;
+      it.last_access = ++m_last_counter;
+      it.value = std::move(value);
+      auto ip = m_items.emplace(key, std::move(it));
+      return &ip.first->second.value;
+    }
+  }
+
+  void Evict(std::size_t count = 1)
+  {
+    while (m_items.size() >= count)
+    {
+      typename MapType::iterator lowest = m_items.end();
+      for (auto iter = m_items.begin(); iter != m_items.end(); ++iter)
+      {
+        if (lowest == m_items.end() || iter->second.last_access < lowest->second.last_access)
+          lowest = iter;
+      }
+      m_items.erase(lowest);
+    }
+  }
+
+  bool Remove(const K& key)
+  {
+    auto iter = m_items.find(key);
+    if (iter == m_items.end())
+      return false;
+
+    m_items.erase(iter);
+    return true;
+  }
+
+private:
+  void ShrinkForNewItem()
+  {
+    if (m_items.size() < m_max_capacity)
+      return;
+
+    Evict(m_items.size() - (m_max_capacity - 1));
+  }
+
+  MapType m_items;
+  CounterType m_last_counter = 0;
+  std::size_t m_max_capacity = 0;
+};
\ No newline at end of file