From 81957070f9549446f13da2b31a4b138df7efe5ae Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 23:42:08 +0100 Subject: [PATCH] Implement an LRU cache --- worldeditadditions/init.lua | 1 + worldeditadditions/utils/lru.lua | 80 ++++++++++++++++++++++++++++++ worldeditadditions/utils/queue.lua | 19 +++++-- 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 worldeditadditions/utils/lru.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 182bdaa..6342b07 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -13,6 +13,7 @@ worldeditadditions.Mesh, worldeditadditions.Face = dofile(worldeditadditions.modpath.."/utils/mesh.lua") worldeditadditions.Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua") +worldeditadditions.LRU = dofile(worldeditadditions.modpath.."/utils/lru.lua") dofile(worldeditadditions.modpath.."/utils/strings/init.lua") diff --git a/worldeditadditions/utils/lru.lua b/worldeditadditions/utils/lru.lua new file mode 100644 index 0000000..0ec0111 --- /dev/null +++ b/worldeditadditions/utils/lru.lua @@ -0,0 +1,80 @@ +local Queue +if worldeditadditions then + Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua") +else + Queue = require("queue") +end + +--- A least-recently-used cache implementation. +-- @class +local LRU = {} +LRU.__index = LRU + +--- Creates a new LRU cache. +-- Optimal sizes: 8, 16, 32, 64 or any value above +-- @param max_size=32 number The maximum number of items to store in the cache. +-- @returns LRU A new LRU cache. +function LRU.new(max_size) + if not max_size then max_size = 32 end + local result = { + max_size = max_size, + cache = { }, + size = 0, + queue = Queue.new() + } + setmetatable(result, LRU) + return result +end + +--- Determines whether the given key is present in this cache object. +-- Does NOT update the most recently used status of said key. +-- @param key string The key to check. +-- @returns bool Whether the given key exists in the cache or not. +function LRU:has(key) + return self.cache[key] ~= nil +end + +--- Gets the value associated with the given key. +-- @param key string The key to retrieve the value for. +-- @returns any|nil The value associated with the given key, or nil if it doesn't exist in this cache. +function LRU:get(key) + if not self.cache[key] then return nil end + + -- Put it to the end of the queue + self.queue:remove_index(self.cache[key].index) + self.cache[key].index = self.queue:enqueue(key) + + return self.cache[key].value +end + +--- Adds a given key-value pair to this cache. +-- Note that this might (or might not) result in the eviction of another item. +-- @param key string The key of the item to add. +-- @param value any The value to associate with the given key. +-- @returns nil +function LRU:set(key, value) + if self.cache[key] ~= nil then + -- It's already present in the cache - update it + -- Put it to the end of the queue + self.queue:remove_index(self.cache[key].index) + local new_index = self.queue:enqueue(key) + -- Update the cache entry + self.cache[key] = { + value = value, + index = new_index + } + else + -- It's not in the cache -- add it + self.cache[key] = { value = value, index = self.queue:enqueue(key) } + + self.size = self.size + 1 + if self.size > self.max_size then + -- The cache is full, delete the oldest item + local oldest_key = self.queue:dequeue() + self.cache[oldest_key] = nil + self.size = self.size - 1 + end + end +end + +return LRU diff --git a/worldeditadditions/utils/queue.lua b/worldeditadditions/utils/queue.lua index 126b7ae..0bbfafc 100644 --- a/worldeditadditions/utils/queue.lua +++ b/worldeditadditions/utils/queue.lua @@ -15,6 +15,7 @@ function Queue:enqueue(value) local new_last = self.last + 1 self.last = new_last self.items[new_last] = value + return new_last end function Queue:contains(value) @@ -30,15 +31,25 @@ function Queue:is_empty() return self.first > self.last end +function Queue:remove_index(index) + self.items[index] = nil +end + function Queue:dequeue() - local first = self.first if Queue.is_empty(self) then error("Error: The self is empty!") end - local value = self.items[first] - self.items[first] = nil -- Help the garbage collector out - self.first = first + 1 + local first = self.first + -- Find the next non-nil item + local value + while value == nil do + if first >= self.last then return nil end + value = self.items[first] + self.items[first] = nil -- Help the garbage collector out + first = first + 1 + end + self.first = first return value end