Merge pull request #352 from psychon/gears_cache

Add gears.cache: a generic cache which may loose values at any time
This commit is contained in:
Daniel Hahler 2015-07-27 13:58:35 +02:00
commit c602eb4ff7
7 changed files with 127 additions and 26 deletions

52
lib/gears/cache.lua Normal file
View File

@ -0,0 +1,52 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2015 Uli Schlachter
-- @release @AWESOME_VERSION@
-- @classmod gears.cache
---------------------------------------------------------------------------
local select = select
local setmetatable = setmetatable
local unpack = unpack or table.unpack
local cache = {}
--- Get an entry from the cache, creating it if it's missing.
-- @param ... Arguments for the creation callback. These are checked against the
-- cache contents for equality.
-- @return The entry from the cache
function cache:get(...)
local result = self._cache
for i = 1, select("#", ...) do
local arg = select(i, ...)
local next = result[arg]
if not next then
next = {}
result[arg] = next
end
result = next
end
local ret = result._entry
if not ret then
ret = { self._creation_cb(...) }
result._entry = ret
end
return unpack(ret)
end
--- Create a new cache object. A cache keeps some data that can be
-- garbage-collected at any time, but might be useful to keep.
-- @param creation_cb Callback that is used for creating missing cache entries.
-- @return A new cache object.
function cache.new(creation_cb)
return setmetatable({
_cache = setmetatable({}, { __mode = "v" }),
_creation_cb = creation_cb
}, {
__index = cache
})
end
return setmetatable(cache, { __call = function(_, ...) return cache.new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -17,7 +17,7 @@ local cairo = require("lgi").cairo
local surface = require("gears.surface") local surface = require("gears.surface")
local color = { mt = {} } local color = { mt = {} }
local pattern_cache = setmetatable({}, { __mode = 'v' }) local pattern_cache
--- Parse a HTML-color. --- Parse a HTML-color.
-- This function can parse colors like `#rrggbb` and `#rrggbbaa`. -- This function can parse colors like `#rrggbb` and `#rrggbbaa`.
@ -231,13 +231,7 @@ function color.create_pattern(col)
if cairo.Pattern:is_type_of(col) then if cairo.Pattern:is_type_of(col) then
return col return col
end end
local col = col or "#000000" return pattern_cache:get(col or "#000000")
local result = pattern_cache[col]
if not result then
result = color.create_pattern_uncached(col)
pattern_cache[col] = result
end
return result
end end
--- Check if a pattern is opaque. --- Check if a pattern is opaque.
@ -296,6 +290,8 @@ function color.mt:__call(...)
return color.create_pattern(...) return color.create_pattern(...)
end end
pattern_cache = require("gears.cache").new(color.create_pattern_uncached)
return setmetatable(color, color.mt) return setmetatable(color, color.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -15,6 +15,7 @@ return
surface = require("gears.surface"); surface = require("gears.surface");
wallpaper = require("gears.wallpaper"); wallpaper = require("gears.wallpaper");
timer = require("gears.timer"); timer = require("gears.timer");
cache = require("gears.cache");
} }
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -38,21 +38,7 @@ function base.fit_widget(widget, width, height)
local width = math.max(0, width) local width = math.max(0, width)
local height = math.max(0, height) local height = math.max(0, height)
-- Since the geometry cache is a weak table, we have to be careful when return widget._fit_geometry_cache:get(width, height)
-- doing lookups. We can't do "if cache[width] ~= nil then"!
local cache = widget._fit_geometry_cache
local result = cache[width]
if not result then
result = {}
cache[width] = result
end
cache, result = result, result[height]
if not result then
local w, h = widget:fit(width, height)
result = { width = w, height = h }
cache[height] = result
end
return result.width, result.height
end end
--- Draw a widget via a cairo context --- Draw a widget via a cairo context

View File

@ -7,6 +7,7 @@
local debug = require("gears.debug") local debug = require("gears.debug")
local object = require("gears.object") local object = require("gears.object")
local cache = require("gears.cache")
local setmetatable = setmetatable local setmetatable = setmetatable
local pairs = pairs local pairs = pairs
local type = type local type = type
@ -98,9 +99,12 @@ function base.make_widget(proxy, widget_name)
end end
-- Add a geometry for base.fit_widget() that is cleared when necessary -- Add a geometry for base.fit_widget() that is cleared when necessary
ret._fit_geometry_cache = setmetatable({}, { __mode = 'v' }) local function cb(...)
return ret:fit(...)
end
ret._fit_geometry_cache = cache.new(cb)
ret:connect_signal("widget::updated", function() ret:connect_signal("widget::updated", function()
ret._fit_geometry_cache = setmetatable({}, { __mode = 'v' }) ret._fit_geometry_cache = cache.new(cb)
end) end)
-- Add __tostring method to metatable. -- Add __tostring method to metatable.

61
spec/gears/cache_spec.lua Normal file
View File

@ -0,0 +1,61 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2015 Uli Schlachter
---------------------------------------------------------------------------
local cache = require("gears.cache")
describe("gears.cache", function()
-- Make sure no cache is cleared during the tests
before_each(function()
collectgarbage("stop")
end)
after_each(function()
collectgarbage("restart")
end)
describe("Zero arguments", function()
it("Creation cb is called", function()
local called = false
local c = cache(function()
called = true
end)
local res = c:get()
assert.is_nil(res)
assert.is_true(called)
end)
end)
describe("Two arguments", function()
it("Cache works", function()
local num_calls = 0
local c = cache(function(a, b)
num_calls = num_calls + 1
return a + b
end)
local res1 = c:get(1, 2)
local res2 = c:get(1, 3)
local res3 = c:get(1, 2)
assert.is.equal(res1, 3)
assert.is.equal(res2, 4)
assert.is.equal(res3, 3)
assert.is.equal(num_calls, 2)
end)
it("Cache invalidation works", function()
local num_calls = 0
local c = cache(function(a, b)
num_calls = num_calls + 1
return a + b
end)
local res1 = c:get(1, 2)
collectgarbage("collect")
local res2 = c:get(1, 2)
assert.is.equal(res1, 3)
assert.is.equal(res2, 3)
assert.is.equal(num_calls, 2)
end)
end)
end)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -4,6 +4,7 @@
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
local object = require("gears.object") local object = require("gears.object")
local cache = require("gears.cache")
local wbase = require("wibox.widget.base") local wbase = require("wibox.widget.base")
local lbase = require("wibox.layout.base") local lbase = require("wibox.layout.base")
local say = require("say") local say = require("say")
@ -64,7 +65,7 @@ return {
return width or 10, height or 10 return width or 10, height or 10
end end
w.draw = function() end w.draw = function() end
w._fit_geometry_cache = {} w._fit_geometry_cache = cache.new(w.fit)
spy.on(w, "fit") spy.on(w, "fit")
stub(w, "draw") stub(w, "draw")