/** \file lru.h Least-recently-used cache implementation */ #ifndef FISH_LRU_H #define FISH_LRU_H #include #include #include #include #include "common.h" /** A predicate to compare dereferenced pointers */ struct dereference_less_t { template bool operator()(ptr_t p1, ptr_t p2) const { return *p1 < *p2; } }; class lru_node_t { template friend class lru_cache_t; /** Our linked list pointer */ lru_node_t *prev, *next; public: /** The key used to look up in the cache */ const wcstring key; /** Constructor */ lru_node_t(const wcstring &pkey) : prev(NULL), next(NULL), key(pkey) { } /** operator< for std::set */ bool operator<(const lru_node_t &other) const { return key < other.key; } }; template class lru_cache_t { private: /** Max node count */ const size_t max_node_count; /** Count of nodes */ size_t node_count; /** The set of nodes */ typedef std::set node_set_t; node_set_t node_set; void promote_node(node_type_t *node) { /* We should never promote the mouth */ assert(node != &mouth); /* First unhook us */ node->prev->next = node->next; node->next->prev = node->prev; /* 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) { /* 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) { /* Simple */ evict_node((node_type_t *)mouth.prev); } protected: /** 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: /** Constructor */ lru_cache_t(size_t max_size = 1024 ) : max_node_count(max_size), node_count(0), mouth(wcstring()) { /* Hook up the mouth to itself: a one node circularly linked list! */ mouth.prev = mouth.next = &mouth; } /** 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() { } /** Returns the node for a given key, or NULL */ node_type_t *get_node(const wcstring &key) { node_type_t *result = NULL; /* Construct a fake node as our key */ lru_node_t node_key(key); /* Look for it in the set */ node_set_t::iterator iter = node_set.find(&node_key); /* If we found a node, promote and return it */ if (iter != node_set.end()) { result = static_cast(*iter); promote_node(result); } return result; } /** Evicts the node for a given key, returning true if a node was evicted. */ bool evict_node(const wcstring &key) { /* Construct a fake node as our key */ lru_node_t node_key(key); /* Look for it in the set */ node_set_t::iterator iter = node_set.find(&node_key); if (iter == node_set.end()) return false; /* Evict the given node */ evict_node(static_cast(*iter)); return true; } /** 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) { /* Add our node without eviction */ if (! this->add_node_without_eviction(node)) return false; /* Evict */ while (node_count > max_node_count) evict_last_node(); /* Success */ return true; } /** 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) { assert(node != NULL && node != &mouth); /* Try inserting; return false if it was already in the set */ if (! node_set.insert(node).second) return false; /* Add the node after the mouth */ node->next = mouth.next; node->next->prev = node; node->prev = &mouth; mouth.next = node; /* Update the count */ node_count++; /* Evict */ while (node_count > max_node_count) evict_last_node(); /* Success */ return true; } /** Counts nodes */ size_t size(void) { return node_count; } /** Evicts all nodes */ void evict_all_nodes(void) { while (node_count > 0) { evict_last_node(); } } /** Iterator for walking nodes, from least recently used to most */ class iterator { lru_node_t *node; public: iterator(lru_node_t *val) : node(val) { } void operator++() { node = node->prev; } void operator++(int x) { node = node->prev; } 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); } }; iterator begin() { return iterator(mouth.prev); } iterator end() { return iterator(&mouth); } }; #endif