diff --git a/lib/gears/cache.lua b/lib/gears/cache.lua new file mode 100644 index 00000000..4f75e205 --- /dev/null +++ b/lib/gears/cache.lua @@ -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 diff --git a/lib/gears/color.lua b/lib/gears/color.lua index 63b0e015..376177df 100644 --- a/lib/gears/color.lua +++ b/lib/gears/color.lua @@ -17,7 +17,7 @@ local cairo = require("lgi").cairo local surface = require("gears.surface") local color = { mt = {} } -local pattern_cache = setmetatable({}, { __mode = 'v' }) +local pattern_cache --- Parse a HTML-color. -- 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 return col end - local col = 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 + return pattern_cache:get(col or "#000000") end --- Check if a pattern is opaque. @@ -296,6 +290,8 @@ function color.mt:__call(...) return color.create_pattern(...) end +pattern_cache = require("gears.cache").new(color.create_pattern_uncached) + return setmetatable(color, color.mt) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/gears/init.lua b/lib/gears/init.lua index c565e7ff..696bed7b 100644 --- a/lib/gears/init.lua +++ b/lib/gears/init.lua @@ -15,6 +15,7 @@ return surface = require("gears.surface"); wallpaper = require("gears.wallpaper"); timer = require("gears.timer"); + cache = require("gears.cache"); } -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/wibox/layout/base.lua b/lib/wibox/layout/base.lua index 1c5b8b83..1a9331bc 100644 --- a/lib/wibox/layout/base.lua +++ b/lib/wibox/layout/base.lua @@ -38,21 +38,7 @@ function base.fit_widget(widget, width, height) local width = math.max(0, width) local height = math.max(0, height) - -- Since the geometry cache is a weak table, we have to be careful when - -- 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 + return widget._fit_geometry_cache:get(width, height) end --- Draw a widget via a cairo context diff --git a/lib/wibox/widget/base.lua b/lib/wibox/widget/base.lua index 6faa2362..fa56f037 100644 --- a/lib/wibox/widget/base.lua +++ b/lib/wibox/widget/base.lua @@ -7,6 +7,7 @@ local debug = require("gears.debug") local object = require("gears.object") +local cache = require("gears.cache") local setmetatable = setmetatable local pairs = pairs local type = type @@ -98,9 +99,12 @@ function base.make_widget(proxy, widget_name) end -- 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._fit_geometry_cache = setmetatable({}, { __mode = 'v' }) + ret._fit_geometry_cache = cache.new(cb) end) -- Add __tostring method to metatable. diff --git a/spec/gears/cache_spec.lua b/spec/gears/cache_spec.lua new file mode 100644 index 00000000..e815a306 --- /dev/null +++ b/spec/gears/cache_spec.lua @@ -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 diff --git a/spec/wibox/test_utils.lua b/spec/wibox/test_utils.lua index 180de905..c3ccbd2d 100644 --- a/spec/wibox/test_utils.lua +++ b/spec/wibox/test_utils.lua @@ -4,6 +4,7 @@ --------------------------------------------------------------------------- local object = require("gears.object") +local cache = require("gears.cache") local wbase = require("wibox.widget.base") local lbase = require("wibox.layout.base") local say = require("say") @@ -64,7 +65,7 @@ return { return width or 10, height or 10 end w.draw = function() end - w._fit_geometry_cache = {} + w._fit_geometry_cache = cache.new(w.fit) spy.on(w, "fit") stub(w, "draw")