Add and use wibox.hierarchy:update()
This function updates a hierarchy if the layout of some widgets changed. It does nothing on the parts that did not change. This should be more efficient than recomputing the whole hierarchy whenever something changes. Once again, this has some positive results on the "benchmark test": Before: create wibox: 0.083016 sec/iter ( 13 iters, 1.161 sec for benchmark) update textclock: 0.00391091 sec/iter (271 iters, 3.219 sec for benchmark) relayout textclock: 0.00273234 sec/iter (397 iters, 1.087 sec for benchmark) redraw textclock: 0.0010191 sec/iter (989 iters, 1.745 sec for benchmark) After: create wibox: 0.083146 sec/iter ( 13 iters, 1.163 sec for benchmark) update textclock: 0.00170519 sec/iter (647 iters, 2.201 sec for benchmark) relayout textclock: 0.000581637 sec/iter (1880 iters, 1.094 sec for benchmark) redraw textclock: 0.0010167 sec/iter (997 iters, 1.773 sec for benchmark) So again no difference for creating wiboxes (100.16% compared to before). This time we also have no real difference for creating wiboxes (99.76%). Update (44%) and relayout (21%) are improved a lot. Closes https://github.com/awesomeWM/awesome/pull/463. Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
parent
f7f8420d24
commit
dfcbf20d81
|
@ -74,19 +74,25 @@ local function do_redraw(self)
|
||||||
-- Relayout
|
-- Relayout
|
||||||
if self._need_relayout or self._need_complete_repaint then
|
if self._need_relayout or self._need_complete_repaint then
|
||||||
self._need_relayout = false
|
self._need_relayout = false
|
||||||
local old_hierarchy = self._widget_hierarchy
|
if self._widget_hierarchy and self.widget then
|
||||||
|
self._widget_hierarchy:update(get_widget_context(self),
|
||||||
|
self.widget, width, height, self._dirty_area)
|
||||||
|
else
|
||||||
|
self._need_complete_repaint = true
|
||||||
|
if self.widget then
|
||||||
self._widget_hierarchy_callback_arg = {}
|
self._widget_hierarchy_callback_arg = {}
|
||||||
self._widget_hierarchy = self.widget and
|
self._widget_hierarchy = hierarchy.new(get_widget_context(self), self.widget, width, height,
|
||||||
hierarchy.new(get_widget_context(self), self.widget, width, height,
|
|
||||||
self._redraw_callback, self._layout_callback, self._widget_hierarchy_callback_arg)
|
self._redraw_callback, self._layout_callback, self._widget_hierarchy_callback_arg)
|
||||||
|
else
|
||||||
|
self._widget_hierarchy = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if old_hierarchy == nil or self._widget_hierarchy == nil or self._need_complete_repaint then
|
if self._need_complete_repaint then
|
||||||
self._need_complete_repaint = false
|
self._need_complete_repaint = false
|
||||||
self._dirty_area:union_rectangle(cairo.RectangleInt{
|
self._dirty_area:union_rectangle(cairo.RectangleInt{
|
||||||
x = 0, y = 0, width = width, height = height
|
x = 0, y = 0, width = width, height = height
|
||||||
})
|
})
|
||||||
else
|
|
||||||
self._dirty_area:union(self._widget_hierarchy:find_differences(old_hierarchy))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,48 +16,40 @@ local no_parent = base.no_parent_I_know_what_I_am_doing
|
||||||
|
|
||||||
local hierarchy = {}
|
local hierarchy = {}
|
||||||
|
|
||||||
local function hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg,
|
local function hierarchy_new(widget, redraw_callback, layout_callback, callback_arg)
|
||||||
matrix_to_parent, matrix_to_device)
|
|
||||||
local children = base.layout_widget(no_parent, context, widget, width, height)
|
|
||||||
local draws_x1, draws_y1, draws_x2, draws_y2 = 0, 0, width, height
|
|
||||||
local result = {
|
local result = {
|
||||||
_matrix = matrix_to_parent,
|
_matrix = matrix.identity,
|
||||||
_matrix_to_device = matrix_to_device,
|
_matrix_to_device = matrix.identity,
|
||||||
_widget = widget,
|
_need_update = true,
|
||||||
|
_widget = nil,
|
||||||
|
_redraw_callback = redraw_callback,
|
||||||
|
_layout_callback = layout_callback,
|
||||||
|
_callback_arg = callback_arg,
|
||||||
_size = {
|
_size = {
|
||||||
width = width,
|
width = nil,
|
||||||
height = height
|
height = nil
|
||||||
},
|
},
|
||||||
_draw_extents = nil,
|
_draw_extents = {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = 0,
|
||||||
|
height = 0
|
||||||
|
},
|
||||||
|
_parent = nil,
|
||||||
_children = {}
|
_children = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
result._redraw = function() redraw_callback(result, callback_arg) end
|
function result._redraw()
|
||||||
result._layout = function() layout_callback(result, callback_arg) end
|
redraw_callback(result, callback_arg)
|
||||||
widget:weak_connect_signal("widget::redraw_needed", result._redraw)
|
end
|
||||||
widget:weak_connect_signal("widget::layout_changed", result._layout)
|
function result._layout()
|
||||||
|
local h = result
|
||||||
for _, w in ipairs(children or {}) do
|
while h do
|
||||||
local r = hierarchy_new(context, w._widget, w._width, w._height, redraw_callback, layout_callback,
|
h._need_update = true
|
||||||
callback_arg, w._matrix, matrix_to_device * w._matrix)
|
h = h._parent
|
||||||
table.insert(result._children, r)
|
end
|
||||||
|
layout_callback(result, callback_arg)
|
||||||
-- Update our drawing extents
|
|
||||||
local s = r._draw_extents
|
|
||||||
local px, py, pwidth, pheight = matrix.transform_rectangle(r._matrix,
|
|
||||||
s.x, s.y, s.width, s.height)
|
|
||||||
local px2, py2 = px + pwidth, py + pheight
|
|
||||||
draws_x1 = math.min(draws_x1, px)
|
|
||||||
draws_y1 = math.min(draws_y1, py)
|
|
||||||
draws_x2 = math.max(draws_x2, px2)
|
|
||||||
draws_y2 = math.max(draws_y2, py2)
|
|
||||||
end
|
end
|
||||||
result._draw_extents = {
|
|
||||||
x = draws_x1,
|
|
||||||
y = draws_y1,
|
|
||||||
width = draws_x2 - draws_x1,
|
|
||||||
height = draws_y2 - draws_y1
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, f in pairs(hierarchy) do
|
for k, f in pairs(hierarchy) do
|
||||||
if type(f) == "function" then
|
if type(f) == "function" then
|
||||||
|
@ -67,6 +59,102 @@ local function hierarchy_new(context, widget, width, height, redraw_callback, la
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local hierarchy_update
|
||||||
|
function hierarchy_update(self, context, widget, width, height, region, matrix_to_parent, matrix_to_device)
|
||||||
|
if (not self._need_update) and self._widget == widget and
|
||||||
|
self._size.width == width and self._size.height == height and
|
||||||
|
matrix.equals(self._matrix, matrix_to_parent) and
|
||||||
|
matrix.equals(self._matrix_to_device, matrix_to_device) then
|
||||||
|
-- Nothing changed
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self._need_update = false
|
||||||
|
|
||||||
|
local old_x, old_y, old_width, old_height
|
||||||
|
local old_widget = self._widget
|
||||||
|
if self._size.width and self._size.height then
|
||||||
|
local x, y, w, h = matrix.transform_rectangle(self._matrix_to_device, 0, 0, self._size.width, self._size.height)
|
||||||
|
old_x, old_y = math.floor(x), math.floor(y)
|
||||||
|
old_width, old_height = math.ceil(x + w) - old_x, math.ceil(y + h) - old_y
|
||||||
|
else
|
||||||
|
old_x, old_y, old_width, old_height = 0, 0, 0, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Disconnect old signals
|
||||||
|
if old_widget and old_widget ~= widget then
|
||||||
|
self._widget:disconnect_signal("widget::redraw_needed", self._redraw)
|
||||||
|
self._widget:disconnect_signal("widget::layout_changed", self._layout)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save the arguments we need to save
|
||||||
|
self._widget = widget
|
||||||
|
self._size.width = width
|
||||||
|
self._size.height = height
|
||||||
|
self._matrix = matrix_to_parent
|
||||||
|
self._matrix_to_device = matrix_to_device
|
||||||
|
|
||||||
|
-- Connect signals
|
||||||
|
if old_widget ~= widget then
|
||||||
|
widget:weak_connect_signal("widget::redraw_needed", self._redraw)
|
||||||
|
widget:weak_connect_signal("widget::layout_changed", self._layout)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update children
|
||||||
|
local old_children = self._children
|
||||||
|
local layout_result = base.layout_widget(no_parent, context, widget, width, height)
|
||||||
|
self._children = {}
|
||||||
|
for _, w in ipairs(layout_result or {}) do
|
||||||
|
local r = table.remove(old_children, 1)
|
||||||
|
if not r then
|
||||||
|
r = hierarchy_new(w._widget, self._redraw_callback, self._layout_callback, self._callback_arg)
|
||||||
|
r._parent = self
|
||||||
|
end
|
||||||
|
hierarchy_update(r, context, w._widget, w._width, w._height, region, w._matrix, matrix_to_device * w._matrix)
|
||||||
|
table.insert(self._children, r)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Calculate the draw extents
|
||||||
|
local x1, y1, x2, y2 = 0, 0, width, height
|
||||||
|
for _, h in ipairs(self._children) do
|
||||||
|
local px, py, pwidth, pheight = matrix.transform_rectangle(h._matrix, h:get_draw_extents())
|
||||||
|
x1 = math.min(x1, px)
|
||||||
|
y1 = math.min(y1, py)
|
||||||
|
x2 = math.max(x2, px + pwidth)
|
||||||
|
y2 = math.max(y2, py + pheight)
|
||||||
|
end
|
||||||
|
self._draw_extents = {
|
||||||
|
x = x1, y = y1,
|
||||||
|
width = x2 - x1,
|
||||||
|
height = y2 - y1
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Check which part needs to be redrawn
|
||||||
|
|
||||||
|
-- Are there any children which were removed? Their area needs a redraw.
|
||||||
|
for _, h in ipairs(old_children) do
|
||||||
|
local x, y, width, height = matrix.transform_rectangle(h._matrix_to_device, h:get_draw_extents())
|
||||||
|
region:union_rectangle(cairo.RectangleInt{
|
||||||
|
x = x, y = y, width = width, height = height
|
||||||
|
})
|
||||||
|
h._parent = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Did we change and need to be redrawn?
|
||||||
|
local x, y, w, h = matrix.transform_rectangle(self._matrix_to_device, 0, 0, self._size.width, self._size.height)
|
||||||
|
local new_x, new_y = math.floor(x), math.floor(y)
|
||||||
|
local new_width, new_height = math.ceil(x + w) - new_x, math.ceil(y + h) - new_y
|
||||||
|
if new_x ~= old_x or new_y ~= old_y or new_width ~= old_width or new_height ~= old_height or
|
||||||
|
widget ~= old_widget then
|
||||||
|
region:union_rectangle(cairo.RectangleInt{
|
||||||
|
x = old_x, y = old_y, width = old_width, height = old_height
|
||||||
|
})
|
||||||
|
region:union_rectangle(cairo.RectangleInt{
|
||||||
|
x = new_x, y = new_y, width = new_width, height = new_height
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- Create a new widget hierarchy that has no parent.
|
--- Create a new widget hierarchy that has no parent.
|
||||||
-- @param context The context in which we are laid out.
|
-- @param context The context in which we are laid out.
|
||||||
-- @param widget The widget that is at the base of the hierarchy.
|
-- @param widget The widget that is at the base of the hierarchy.
|
||||||
|
@ -79,8 +167,23 @@ end
|
||||||
-- @param callback_arg A second argument that is given to the above callbacks.
|
-- @param callback_arg A second argument that is given to the above callbacks.
|
||||||
-- @return A new widget hierarchy
|
-- @return A new widget hierarchy
|
||||||
function hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, callback_arg)
|
function hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, callback_arg)
|
||||||
return hierarchy_new(context, widget, width, height, redraw_callback, layout_callback, callback_arg,
|
local result = hierarchy_new(widget, redraw_callback, layout_callback, callback_arg)
|
||||||
matrix.identity, matrix.identity)
|
result:update(context, widget, width, height)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Update a widget hierarchy with some new state.
|
||||||
|
-- @param context The context in which we are laid out.
|
||||||
|
-- @param widget The widget that is at the base of the hierarchy.
|
||||||
|
-- @param width The available width for this hierarchy.
|
||||||
|
-- @param height The available height for this hierarchy.
|
||||||
|
-- @param[opt] region A region to use for accumulating changed parts
|
||||||
|
-- @return A cairo region describing the changed parts (either the `region`
|
||||||
|
-- argument or a new, internally created region).
|
||||||
|
function hierarchy:update(context, widget, width, height, region)
|
||||||
|
local region = region or cairo.Region.create()
|
||||||
|
hierarchy_update(self, context, widget, width, height, region, self._matrix, self._matrix_to_device)
|
||||||
|
return region
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the widget that this hierarchy manages.
|
--- Get the widget that this hierarchy manages.
|
||||||
|
@ -142,40 +245,6 @@ function hierarchy:get_children()
|
||||||
return self._children
|
return self._children
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Compare two widget hierarchies and compute a cairo Region that contains all
|
|
||||||
-- rectangles that aren't the same between both hierarchies.
|
|
||||||
-- @param other The hierarchy to compare with
|
|
||||||
-- @return A cairo Region containing the differences.
|
|
||||||
function hierarchy:find_differences(other)
|
|
||||||
local region = cairo.Region.create()
|
|
||||||
local function needs_redraw(h)
|
|
||||||
local m = h:get_matrix_to_device()
|
|
||||||
local p = h._draw_extents
|
|
||||||
local x, y, width, height = matrix.transform_rectangle(m, p.x, p.y, p.width, p.height)
|
|
||||||
local x1, y1 = math.floor(x), math.floor(y)
|
|
||||||
local x2, y2 = math.ceil(x + width), math.ceil(y + height)
|
|
||||||
region:union_rectangle(cairo.RectangleInt({
|
|
||||||
x = x1, y = y1, width = x2 - x1, height = y2 - y1
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
local compare
|
|
||||||
compare = function(self, other)
|
|
||||||
local s_size, o_size = self._size, other._size
|
|
||||||
if s_size.width ~= o_size.width or s_size.height ~= o_size.height or
|
|
||||||
#self._children ~= #other._children or self._widget ~= other._widget or
|
|
||||||
not matrix.equals(self._matrix, other._matrix) then
|
|
||||||
needs_redraw(self)
|
|
||||||
needs_redraw(other)
|
|
||||||
else
|
|
||||||
for i = 1, #self._children do
|
|
||||||
compare(self._children[i], other._children[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
compare(self, other)
|
|
||||||
return region
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Does the given cairo context have an empty clip (aka "no drawing possible")?
|
--- Does the given cairo context have an empty clip (aka "no drawing possible")?
|
||||||
local function empty_clip(cr)
|
local function empty_clip(cr)
|
||||||
local x, y, width, height = cr:clip_extents()
|
local x, y, width, height = cr:clip_extents()
|
||||||
|
|
|
@ -188,7 +188,7 @@ describe("wibox.hierarchy", function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("find_differences", function()
|
describe("update", function()
|
||||||
local child, intermediate, parent
|
local child, intermediate, parent
|
||||||
local instance
|
local instance
|
||||||
local function nop() end
|
local function nop() end
|
||||||
|
@ -205,25 +205,60 @@ describe("wibox.hierarchy", function()
|
||||||
instance = hierarchy.new(context, parent, 15, 16, nop, nop)
|
instance = hierarchy.new(context, parent, 15, 16, nop, nop)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("No difference", function()
|
it("No difference 1", function()
|
||||||
local context = {}
|
local region = instance:update(context, parent, 15, 16)
|
||||||
local instance2 = hierarchy.new(context, parent, 15, 16, nop, nop)
|
|
||||||
local region = instance:find_differences(instance2)
|
|
||||||
assert.is.equal(region:num_rectangles(), 0)
|
assert.is.equal(region:num_rectangles(), 0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("No difference 2", function()
|
||||||
|
local region1 = Region.create()
|
||||||
|
local region2 = instance:update(context, parent, 15, 16, region1)
|
||||||
|
assert.is.equal(region1, region2)
|
||||||
|
assert.is.equal(region2:num_rectangles(), 0)
|
||||||
|
end)
|
||||||
|
|
||||||
it("child moved", function()
|
it("child moved", function()
|
||||||
|
-- Clear caches and change result of intermediate
|
||||||
intermediate.layout = function()
|
intermediate.layout = function()
|
||||||
return { make_child(child, 10, 20, matrix.create_translate(0, 4)) }
|
return { make_child(child, 10, 20, matrix.create_translate(0, 4)) }
|
||||||
end
|
end
|
||||||
local context = {}
|
intermediate:emit_signal("widget::layout_changed")
|
||||||
local instance2 = hierarchy.new(context, parent, 15, 16, nop, nop)
|
|
||||||
local region = instance:find_differences(instance2)
|
local region = instance:update(context, parent, 15, 16)
|
||||||
assert.is.equal(region:num_rectangles(), 1)
|
assert.is.equal(region:num_rectangles(), 1)
|
||||||
local rect = region:get_rectangle(0)
|
local rect = region:get_rectangle(0)
|
||||||
-- The widget drew to 4, 5, 10, 20 before and 4, 4, 10, 20 after
|
-- The widget drew to 4, 5, 10, 20 before and 4, 4, 10, 20 after
|
||||||
assert.is.same({ rect.x, rect.y, rect.width, rect.height }, { 4, 4, 10, 21 })
|
assert.is.same({ rect.x, rect.y, rect.width, rect.height }, { 4, 4, 10, 21 })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("child disappears", function()
|
||||||
|
-- Clear caches and change result of intermediate
|
||||||
|
intermediate.layout = function() end
|
||||||
|
intermediate:emit_signal("widget::layout_changed")
|
||||||
|
|
||||||
|
local region = instance:update(context, parent, 15, 16)
|
||||||
|
assert.is.equal(region:num_rectangles(), 1)
|
||||||
|
local rect = region:get_rectangle(0)
|
||||||
|
-- The child was drawn to 4, 5, 10, 20
|
||||||
|
assert.is.same({ rect.x, rect.y, rect.width, rect.height }, { 4, 5, 10, 20 })
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("widget changed", function()
|
||||||
|
-- Clear caches and change result of parent
|
||||||
|
local new_intermediate = make_widget({
|
||||||
|
make_child(child, 10, 20, matrix.create_translate(0, 5))
|
||||||
|
})
|
||||||
|
parent.layout = function()
|
||||||
|
return { make_child(new_intermediate, 5, 2, matrix.create_translate(4, 0)) }
|
||||||
|
end
|
||||||
|
parent:emit_signal("widget::layout_changed")
|
||||||
|
|
||||||
|
local region = instance:update(context, parent, 15, 16)
|
||||||
|
assert.is.equal(region:num_rectangles(), 1)
|
||||||
|
local rect = region:get_rectangle(0)
|
||||||
|
-- Intermediate drew to 4, 0, 5, 2 (and so does new_intermediate)
|
||||||
|
assert.is.same({ rect.x, rect.y, rect.width, rect.height }, { 4, 0, 5, 2 })
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue