mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
Simplifies access to the cache without moving code around a lot given the currently common pattern of local some_cache = cache.new(size, function(k,v) end)
183 lines
3.8 KiB
Lua
183 lines
3.8 KiB
Lua
|
|
local function _remove(list, m)
|
|
if m.prev then
|
|
m.prev.next = m.next;
|
|
end
|
|
if m.next then
|
|
m.next.prev = m.prev;
|
|
end
|
|
if list._tail == m then
|
|
list._tail = m.prev;
|
|
end
|
|
if list._head == m then
|
|
list._head = m.next;
|
|
end
|
|
list._count = list._count - 1;
|
|
end
|
|
|
|
local function _insert(list, m)
|
|
if list._head then
|
|
list._head.prev = m;
|
|
end
|
|
m.prev, m.next = nil, list._head;
|
|
list._head = m;
|
|
if not list._tail then
|
|
list._tail = m;
|
|
end
|
|
list._count = list._count + 1;
|
|
end
|
|
|
|
local cache_methods = {};
|
|
local cache_mt = { __name = "cache", __index = cache_methods };
|
|
|
|
function cache_methods:set(k, v)
|
|
local m = self._data[k];
|
|
if m then
|
|
-- Key already exists
|
|
if v ~= nil then
|
|
-- Bump to head of list
|
|
_remove(self, m);
|
|
_insert(self, m);
|
|
m.value = v;
|
|
else
|
|
-- Remove from list
|
|
_remove(self, m);
|
|
self._data[k] = nil;
|
|
end
|
|
return true;
|
|
end
|
|
-- New key
|
|
if v == nil then
|
|
return true;
|
|
end
|
|
-- Check whether we need to remove oldest k/v
|
|
if self._count == self.size then
|
|
local tail = self._tail;
|
|
local on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;
|
|
|
|
local do_evict = on_evict and on_evict(evicted_key, evicted_value, self);
|
|
|
|
if do_evict == false then
|
|
-- Cache is full, and we're not allowed to evict
|
|
return false;
|
|
elseif self._count == self.size then
|
|
-- Cache wasn't grown
|
|
_remove(self, tail);
|
|
self._data[evicted_key] = nil;
|
|
end
|
|
end
|
|
|
|
m = { key = k, value = v, prev = nil, next = nil };
|
|
self._data[k] = m;
|
|
_insert(self, m);
|
|
return true;
|
|
end
|
|
|
|
function cache_methods:get(k)
|
|
local m = self._data[k];
|
|
if m then
|
|
return m.value;
|
|
end
|
|
return nil;
|
|
end
|
|
|
|
function cache_methods:items()
|
|
local m = self._head;
|
|
return function ()
|
|
if not m then
|
|
return;
|
|
end
|
|
local k, v = m.key, m.value;
|
|
m = m.next;
|
|
return k, v;
|
|
end
|
|
end
|
|
|
|
function cache_methods:values()
|
|
local m = self._head;
|
|
return function ()
|
|
if not m then
|
|
return;
|
|
end
|
|
local v = m.value;
|
|
m = m.next;
|
|
return v;
|
|
end
|
|
end
|
|
|
|
function cache_methods:count()
|
|
return self._count;
|
|
end
|
|
|
|
function cache_methods:head()
|
|
local head = self._head;
|
|
if not head then return nil, nil; end
|
|
return head.key, head.value;
|
|
end
|
|
|
|
function cache_methods:tail()
|
|
local tail = self._tail;
|
|
if not tail then return nil, nil; end
|
|
return tail.key, tail.value;
|
|
end
|
|
|
|
function cache_methods:resize(new_size)
|
|
new_size = assert(tonumber(new_size), "cache size must be a number");
|
|
new_size = math.floor(new_size);
|
|
assert(new_size > 0, "cache size must be greater than zero");
|
|
local on_evict = self._on_evict;
|
|
while self._count > new_size do
|
|
local tail = self._tail;
|
|
local evicted_key, evicted_value = tail.key, tail.value;
|
|
if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value, self) == false) then
|
|
-- Cache is full, and we're not allowed to evict
|
|
return false;
|
|
end
|
|
_remove(self, tail);
|
|
self._data[evicted_key] = nil;
|
|
end
|
|
self.size = new_size;
|
|
return true;
|
|
end
|
|
|
|
function cache_methods:table()
|
|
--luacheck: ignore 212/t
|
|
if not self.proxy_table then
|
|
self.proxy_table = setmetatable({}, {
|
|
__index = function (t, k)
|
|
return self:get(k);
|
|
end;
|
|
__newindex = function (t, k, v)
|
|
if not self:set(k, v) then
|
|
error("failed to insert key into cache - full");
|
|
end
|
|
end;
|
|
__pairs = function (t)
|
|
return self:items();
|
|
end;
|
|
__len = function (t)
|
|
return self:count();
|
|
end;
|
|
});
|
|
end
|
|
return self.proxy_table;
|
|
end
|
|
|
|
function cache_methods:clear()
|
|
self._data = {};
|
|
self._count = 0;
|
|
self._head = nil;
|
|
self._tail = nil;
|
|
end
|
|
|
|
local function new(size, on_evict)
|
|
size = assert(tonumber(size), "cache size must be a number");
|
|
size = math.floor(size);
|
|
assert(size > 0, "cache size must be greater than zero");
|
|
local data = {};
|
|
return setmetatable({ _data = data, _count = 0, size = size, _head = nil, _tail = nil, _on_evict = on_evict }, cache_mt);
|
|
end
|
|
|
|
return {
|
|
new = new;
|
|
}
|