2019-09-10 12:34:57 +02:00

240 lines
5.7 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (C) 2016 Flössie <floessie.mail@gmail.com>
*
* RawTherapee is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RawTherapee is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <algorithm>
#include <list>
#include <map>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include "../rtgui/threadutils.h"
namespace rtengine
{
namespace cache_helper
{
// See http://stackoverflow.com/a/20790050
template<typename, typename = void>
struct has_hash
: std::false_type
{
};
template<typename T>
struct has_hash<T, decltype(std::hash<T>()(std::declval<T>()), void())>
: std::true_type
{
};
}
template<class K, class V>
class Cache
{
public:
class Hook
{
public:
virtual ~Hook()
{
}
virtual void onDiscard(const K& key, const V& value) = 0;
virtual void onDisplace(const K& key, const V& value) = 0;
virtual void onRemove(const K& key, const V& value) = 0;
virtual void onDestroy() = 0;
};
Cache(unsigned long _size, Hook* _hook = nullptr) :
store_size(_size),
hook(_hook)
{
}
~Cache()
{
if (hook) {
resize(0);
hook->onDestroy();
}
}
bool get(const K& key, V& value) const
{
mutex.lock();
const StoreConstIterator store_it = store.find(key);
const bool present = store_it != store.end();
if (present) {
lru_list.splice(
lru_list.begin(),
lru_list,
store_it->second->lru_list_it
);
value = store_it->second->value;
}
mutex.unlock();
return present;
}
bool set(const K& key, const V& value)
{
return set(key, value, Mode::UNCOND);
}
bool replace(const K& key, const V& value)
{
return set(key, value, Mode::KNOWN);
}
bool insert(const K& key, const V& value)
{
return set(key, value, Mode::UNKNOWN);
}
bool remove(const K& key)
{
mutex.lock();
const StoreIterator store_it = store.find(key);
const bool present = store_it != store.end();
if (present) {
remove(store_it);
}
mutex.unlock();
return present;
}
void resize(unsigned long size)
{
mutex.lock();
while (lru_list.size() > size) {
discard();
}
store_size = size;
mutex.unlock();
}
void clear()
{
mutex.lock();
if (hook) {
for (const auto& entry : store) {
hook->onRemove(entry.first, entry.second->value);
}
}
lru_list.clear();
store.clear();
mutex.unlock();
}
private:
struct Value;
using Store = typename std::conditional<
cache_helper::has_hash<K>::value,
std::unordered_map<K, std::unique_ptr<Value>>,
std::map<K, std::unique_ptr<Value>>
>::type;
using StoreIterator = typename Store::iterator;
using StoreConstIterator = typename Store::const_iterator;
using LruList = std::list<StoreIterator>;
using LruListIterator = typename LruList::iterator;
struct Value {
V value;
LruListIterator lru_list_it;
};
enum class Mode {
UNCOND,
KNOWN,
UNKNOWN
};
void discard()
{
const StoreIterator store_it = lru_list.back();
if (hook) {
hook->onDiscard(store_it->first, store_it->second->value);
}
store.erase(store_it);
lru_list.pop_back();
}
bool set(const K& key, const V& value, Mode mode)
{
mutex.lock();
const StoreIterator store_it = store.find(key);
const bool is_new_key = store_it == store.end();
if (is_new_key) {
if (mode == Mode::UNCOND || mode == Mode::UNKNOWN) {
if (lru_list.size() >= store_size) {
discard();
}
lru_list.push_front(store.end());
std::unique_ptr<Value> v(
new Value{
value,
lru_list.begin()
}
);
lru_list.front() = store.emplace(key, std::move(v)).first;
}
} else {
if (mode == Mode::UNCOND || mode == Mode::KNOWN) {
if (hook) {
hook->onDisplace(key, store_it->second->value);
}
lru_list.splice(
lru_list.begin(),
lru_list,
store_it->second->lru_list_it
);
store_it->second->value = value;
}
}
mutex.unlock();
return is_new_key;
}
void remove(const StoreIterator& store_it)
{
if (hook) {
hook->onRemove(store_it->first, store_it->second->value);
}
lru_list.erase(store_it->second->lru_list_it);
store.erase(store_it);
}
unsigned long store_size;
Hook* const hook;
mutable MyMutex mutex;
Store store;
mutable LruList lru_list;
};
}