fish-shell/src/lru.h

258 lines
6.4 KiB
C
Raw Normal View History

2012-02-06 12:54:41 +08:00
/** \file lru.h
Least-recently-used cache implementation
*/
#ifndef FISH_LRU_H
#define FISH_LRU_H
2015-07-25 23:14:25 +08:00
#include <assert.h>
2012-02-06 12:54:41 +08:00
#include <wchar.h>
#include <map>
#include <set>
#include <list>
#include "common.h"
/** A predicate to compare dereferenced pointers */
struct dereference_less_t
{
2012-02-06 12:54:41 +08:00
template <typename ptr_t>
bool operator()(ptr_t p1, ptr_t p2) const
{
return *p1 < *p2;
}
2012-02-06 12:54:41 +08:00
};
class lru_node_t
{
2012-02-06 12:54:41 +08:00
template<class T> friend class lru_cache_t;
2012-02-06 12:54:41 +08:00
/** Our linked list pointer */
lru_node_t *prev, *next;
2012-02-06 12:54:41 +08:00
public:
/** The key used to look up in the cache */
const wcstring key;
2012-02-06 12:54:41 +08:00
/** Constructor */
explicit lru_node_t(const wcstring &pkey) : prev(NULL), next(NULL), key(pkey) { }
/** Virtual destructor that does nothing for classes that inherit lru_node_t */
virtual ~lru_node_t() {}
2012-02-06 12:54:41 +08:00
/** operator< for std::set */
bool operator<(const lru_node_t &other) const
{
return key < other.key;
}
2012-02-06 12:54:41 +08:00
};
template<class node_type_t>
class lru_cache_t
{
private:
2012-02-06 12:54:41 +08:00
/** Max node count. This may be (transiently) exceeded by add_node_without_eviction, which is used from background threads. */
2012-02-06 12:54:41 +08:00
const size_t max_node_count;
2012-02-06 12:54:41 +08:00
/** Count of nodes */
size_t node_count;
2012-02-06 12:54:41 +08:00
/** The set of nodes */
typedef std::set<lru_node_t *, dereference_less_t> node_set_t;
node_set_t node_set;
void promote_node(node_type_t *node)
{
2012-02-06 12:54:41 +08:00
/* We should never promote the mouth */
assert(node != &mouth);
2012-02-06 12:54:41 +08:00
/* First unhook us */
node->prev->next = node->next;
node->next->prev = node->prev;
2012-02-06 12:54:41 +08:00
/* Put us after the mouth */
node->next = mouth.next;
node->next->prev = node;
node->prev = &mouth;
mouth.next = node;
}
void evict_node(node_type_t *condemned_node)
{
2012-02-06 12:54:41 +08:00
/* We should never evict the mouth */
assert(condemned_node != NULL && condemned_node != &mouth);
/* Remove it from the linked list */
condemned_node->prev->next = condemned_node->next;
condemned_node->next->prev = condemned_node->prev;
/* Remove us from the set */
node_set.erase(condemned_node);
node_count--;
/* Tell ourselves */
this->node_was_evicted(condemned_node);
}
void evict_last_node(void)
{
2012-02-06 12:54:41 +08:00
/* Simple */
evict_node((node_type_t *)mouth.prev);
}
2012-12-12 05:18:40 +08:00
static lru_node_t *get_previous(lru_node_t *node)
{
return node->prev;
}
protected:
2012-02-06 12:54:41 +08:00
/** Head of the linked list */
lru_node_t mouth;
/** Overridable callback for when a node is evicted */
virtual void node_was_evicted(node_type_t *node) { }
public:
2012-02-06 12:54:41 +08:00
/** Constructor */
explicit lru_cache_t(size_t max_size = 1024) : max_node_count(max_size), node_count(0), mouth(wcstring())
{
2012-02-06 12:54:41 +08:00
/* Hook up the mouth to itself: a one node circularly linked list! */
mouth.prev = mouth.next = &mouth;
}
2012-05-05 09:37:12 +08:00
/** Note that we do not evict nodes in our destructor (even though they typically need to be deleted by their creator). */
virtual ~lru_cache_t() { }
2012-02-18 06:54:58 +08:00
2012-02-06 12:54:41 +08:00
/** Returns the node for a given key, or NULL */
node_type_t *get_node(const wcstring &key)
{
2012-02-06 12:54:41 +08:00
node_type_t *result = NULL;
2012-02-06 12:54:41 +08:00
/* Construct a fake node as our key */
lru_node_t node_key(key);
2012-02-06 12:54:41 +08:00
/* Look for it in the set */
node_set_t::iterator iter = node_set.find(&node_key);
2012-02-06 12:54:41 +08:00
/* If we found a node, promote and return it */
if (iter != node_set.end())
{
2012-02-06 12:54:41 +08:00
result = static_cast<node_type_t*>(*iter);
promote_node(result);
}
return result;
}
2012-02-06 12:54:41 +08:00
/** Evicts the node for a given key, returning true if a node was evicted. */
bool evict_node(const wcstring &key)
{
2012-02-06 12:54:41 +08:00
/* Construct a fake node as our key */
lru_node_t node_key(key);
2012-02-06 12:54:41 +08:00
/* Look for it in the set */
node_set_t::iterator iter = node_set.find(&node_key);
if (iter == node_set.end())
return false;
2012-02-06 12:54:41 +08:00
/* Evict the given node */
evict_node(static_cast<node_type_t*>(*iter));
return true;
}
2012-02-06 12:54:41 +08:00
/** Adds a node under the given key. Returns true if the node was added, false if the node was not because a node with that key is already in the set. */
bool add_node(node_type_t *node)
{
2012-02-06 12:54:41 +08:00
/* Add our node without eviction */
if (! this->add_node_without_eviction(node))
return false;
2012-02-06 12:54:41 +08:00
/* Evict */
while (node_count > max_node_count)
evict_last_node();
2012-02-06 12:54:41 +08:00
/* Success */
return true;
}
2012-02-06 12:54:41 +08:00
/** Adds a node under the given key without triggering eviction. Returns true if the node was added, false if the node was not because a node with that key is already in the set. */
bool add_node_without_eviction(node_type_t *node)
{
2012-02-06 12:54:41 +08:00
assert(node != NULL && node != &mouth);
2012-02-06 12:54:41 +08:00
/* Try inserting; return false if it was already in the set */
if (! node_set.insert(node).second)
return false;
2012-02-06 12:54:41 +08:00
/* Add the node after the mouth */
node->next = mouth.next;
node->next->prev = node;
node->prev = &mouth;
mouth.next = node;
/* Update the count. This may push us over the maximum node count. */
2012-02-06 12:54:41 +08:00
node_count++;
2012-02-06 12:54:41 +08:00
/* Success */
return true;
}
2012-02-06 12:54:41 +08:00
/** Counts nodes */
size_t size(void)
{
2012-02-06 12:54:41 +08:00
return node_count;
}
2012-02-06 12:54:41 +08:00
/** Evicts all nodes */
void evict_all_nodes(void)
{
while (node_count > 0)
{
2012-02-06 12:54:41 +08:00
evict_last_node();
}
}
2012-02-06 12:54:41 +08:00
/** Iterator for walking nodes, from least recently used to most */
class iterator
{
2012-02-06 12:54:41 +08:00
lru_node_t *node;
public:
explicit iterator(lru_node_t *val) : node(val) { }
void operator++()
{
2012-12-12 05:18:40 +08:00
node = lru_cache_t::get_previous(node);
}
void operator++(int x)
{
2012-12-12 05:18:40 +08:00
node = lru_cache_t::get_previous(node);
}
bool operator==(const iterator &other)
{
return node == other.node;
}
bool operator!=(const iterator &other)
{
return !(*this == other);
}
node_type_t *operator*()
{
return static_cast<node_type_t *>(node);
}
2012-02-06 12:54:41 +08:00
};
iterator begin()
{
return iterator(mouth.prev);
}
iterator end()
{
return iterator(&mouth);
}
2012-02-06 12:54:41 +08:00
};
#endif