|
@@ -28,9 +28,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
#include <map>
|
|
|
#include <set>
|
|
|
#include <queue>
|
|
|
+#include <cassert>
|
|
|
+#include <limits>
|
|
|
|
|
|
/*
|
|
|
-Queue with unique values with fast checking of value existence
|
|
|
+ Queue with unique values with fast checking of value existence
|
|
|
*/
|
|
|
|
|
|
template<typename Value>
|
|
@@ -75,6 +77,10 @@ private:
|
|
|
std::queue<Value> m_queue;
|
|
|
};
|
|
|
|
|
|
+/*
|
|
|
+ Thread-safe map
|
|
|
+*/
|
|
|
+
|
|
|
template<typename Key, typename Value>
|
|
|
class MutexedMap
|
|
|
{
|
|
@@ -116,7 +122,9 @@ private:
|
|
|
};
|
|
|
|
|
|
|
|
|
-// Thread-safe Double-ended queue
|
|
|
+/*
|
|
|
+ Thread-safe double-ended queue
|
|
|
+*/
|
|
|
|
|
|
template<typename T>
|
|
|
class MutexedQueue
|
|
@@ -237,6 +245,10 @@ protected:
|
|
|
Semaphore m_signal;
|
|
|
};
|
|
|
|
|
|
+/*
|
|
|
+ LRU cache
|
|
|
+*/
|
|
|
+
|
|
|
template<typename K, typename V>
|
|
|
class LRUCache
|
|
|
{
|
|
@@ -305,3 +317,206 @@ private:
|
|
|
// we can't use std::deque here, because its iterators get invalidated
|
|
|
std::list<K> m_queue;
|
|
|
};
|
|
|
+
|
|
|
+/*
|
|
|
+ Map that can be safely modified (insertion, deletion) during iteration
|
|
|
+ Caveats:
|
|
|
+ - you cannot insert null elements
|
|
|
+ - you have to check for null elements during iteration, those are ones already deleted
|
|
|
+ - size() and empty() don't work during iteration
|
|
|
+ - not thread-safe in any way
|
|
|
+
|
|
|
+ How this is implemented:
|
|
|
+ - there are two maps: new and real
|
|
|
+ - if inserting duration iteration, the value is inserted into the "new" map
|
|
|
+ - if deleting during iteration, the value is set to null (to be GC'd later)
|
|
|
+ - when iteration finishes the "new" map is merged into the "real" map
|
|
|
+*/
|
|
|
+
|
|
|
+template<typename K, typename V>
|
|
|
+class ModifySafeMap
|
|
|
+{
|
|
|
+public:
|
|
|
+ // this allows bare pointers but also e.g. std::unique_ptr
|
|
|
+ static_assert(std::is_default_constructible<V>::value,
|
|
|
+ "Value type must be default constructible");
|
|
|
+ static_assert(std::is_constructible<bool, V>::value,
|
|
|
+ "Value type must be convertible to bool");
|
|
|
+ static_assert(std::is_move_assignable<V>::value,
|
|
|
+ "Value type must be move-assignable");
|
|
|
+
|
|
|
+ typedef K key_type;
|
|
|
+ typedef V mapped_type;
|
|
|
+
|
|
|
+ ModifySafeMap() {
|
|
|
+ // the null value must convert to false and all others to true, but
|
|
|
+ // we can't statically check the latter.
|
|
|
+ sanity_check(!null_value);
|
|
|
+ }
|
|
|
+ ~ModifySafeMap() {
|
|
|
+ assert(!m_iterating);
|
|
|
+ }
|
|
|
+
|
|
|
+ // possible to implement but we don't need it
|
|
|
+ DISABLE_CLASS_COPY(ModifySafeMap)
|
|
|
+ ALLOW_CLASS_MOVE(ModifySafeMap)
|
|
|
+
|
|
|
+ const V &get(const K &key) const {
|
|
|
+ if (m_iterating) {
|
|
|
+ auto it = m_new.find(key);
|
|
|
+ if (it != m_new.end())
|
|
|
+ return it->second;
|
|
|
+ }
|
|
|
+ auto it = m_values.find(key);
|
|
|
+ return it == m_values.end() ? null_value : it->second;
|
|
|
+ }
|
|
|
+
|
|
|
+ void put(const K &key, const V &value) {
|
|
|
+ if (!value) {
|
|
|
+ assert(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (m_iterating)
|
|
|
+ m_new.emplace(key, value);
|
|
|
+ else
|
|
|
+ m_values.emplace(key, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ void put(const K &key, V &&value) {
|
|
|
+ if (!value) {
|
|
|
+ assert(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (m_iterating)
|
|
|
+ m_new.emplace(key, std::move(value));
|
|
|
+ else
|
|
|
+ m_values.emplace(key, std::move(value));
|
|
|
+ }
|
|
|
+
|
|
|
+ V take(const K &key) {
|
|
|
+ V ret = V();
|
|
|
+ if (m_iterating) {
|
|
|
+ auto it = m_new.find(key);
|
|
|
+ if (it != m_new.end()) {
|
|
|
+ ret = std::move(it->second);
|
|
|
+ m_new.erase(it);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ auto it = m_values.find(key);
|
|
|
+ if (it == m_values.end())
|
|
|
+ return ret;
|
|
|
+ ret = std::move(it->second);
|
|
|
+ if (m_iterating) {
|
|
|
+ it->second = V();
|
|
|
+ m_garbage++;
|
|
|
+ } else {
|
|
|
+ m_values.erase(it);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool remove(const K &key) {
|
|
|
+ return !!take(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Warning: not constant-time!
|
|
|
+ size_t size() const {
|
|
|
+ if (m_iterating) {
|
|
|
+ // This is by no means impossible to determine, it's just annoying
|
|
|
+ // to code and we happen to not need this.
|
|
|
+ return unknown;
|
|
|
+ }
|
|
|
+ assert(m_new.empty());
|
|
|
+ if (m_garbage == 0)
|
|
|
+ return m_values.size();
|
|
|
+ size_t n = 0;
|
|
|
+ for (auto &it : m_values)
|
|
|
+ n += !it.second ? 0 : 1;
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Warning: not constant-time!
|
|
|
+ bool empty() const {
|
|
|
+ if (m_iterating)
|
|
|
+ return false; // maybe
|
|
|
+ if (m_garbage == 0)
|
|
|
+ return m_values.empty();
|
|
|
+ for (auto &it : m_values) {
|
|
|
+ if (!!it.second)
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto iter() { return IterationHelper(this); }
|
|
|
+
|
|
|
+ void clear() {
|
|
|
+ if (m_iterating) {
|
|
|
+ for (auto &it : m_values)
|
|
|
+ it.second = V();
|
|
|
+ m_garbage = m_values.size();
|
|
|
+ } else {
|
|
|
+ m_values.clear();
|
|
|
+ m_garbage = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static inline const V null_value = V();
|
|
|
+
|
|
|
+ // returned by size() if called during iteration
|
|
|
+ static constexpr size_t unknown = static_cast<size_t>(-1);
|
|
|
+
|
|
|
+protected:
|
|
|
+ void merge_new() {
|
|
|
+ assert(!m_iterating);
|
|
|
+ if (!m_new.empty()) {
|
|
|
+ m_new.merge(m_values); // entries in m_new take precedence
|
|
|
+ m_values.clear();
|
|
|
+ std::swap(m_values, m_new);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void collect_garbage() {
|
|
|
+ assert(!m_iterating);
|
|
|
+ if (m_values.size() < GC_MIN_SIZE || m_garbage < m_values.size() / 2)
|
|
|
+ return;
|
|
|
+ for (auto it = m_values.begin(); it != m_values.end(); ) {
|
|
|
+ if (!it->second)
|
|
|
+ it = m_values.erase(it);
|
|
|
+ else
|
|
|
+ ++it;
|
|
|
+ }
|
|
|
+ m_garbage = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct IterationHelper {
|
|
|
+ friend class ModifySafeMap<K, V>;
|
|
|
+ ~IterationHelper() {
|
|
|
+ assert(m->m_iterating);
|
|
|
+ m->m_iterating--;
|
|
|
+ if (!m->m_iterating) {
|
|
|
+ m->merge_new();
|
|
|
+ m->collect_garbage();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ auto begin() { return m->m_values.cbegin(); }
|
|
|
+ auto end() { return m->m_values.cend(); }
|
|
|
+
|
|
|
+ private:
|
|
|
+ IterationHelper(ModifySafeMap<K, V> *parent) : m(parent) {
|
|
|
+ assert(m->m_iterating < std::numeric_limits<decltype(m_iterating)>::max());
|
|
|
+ m->m_iterating++;
|
|
|
+ }
|
|
|
+
|
|
|
+ ModifySafeMap<K, V> *m;
|
|
|
+ };
|
|
|
+
|
|
|
+private:
|
|
|
+ std::map<K, V> m_values;
|
|
|
+ std::map<K, V> m_new;
|
|
|
+ unsigned int m_iterating = 0;
|
|
|
+ size_t m_garbage = 0; // upper bound for null-placeholders in m_values
|
|
|
+
|
|
|
+ static constexpr size_t GC_MIN_SIZE = 30;
|
|
|
+};
|