-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimize performance of results tree
The GUI is very slow when the number of tests exceeds 500. The reason for this is the tree component that displays the test names. Its implementation is inefficient. In each frame, this component draws all its content - even those invisible to the user. This commit changes the implementation of the tree component to a more efficient one. The new implementation only draws the part of the tree that is currently presented to the user. This component also uses a minimal amount of RAM, which allows you to run several thousand tests.
- Loading branch information
Showing
5 changed files
with
175 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,120 +1,70 @@ | ||
--[[pod_format="raw",created="2024-11-09 17:24:35",modified="2024-11-09 17:24:35",revision=0]] | ||
-- (c) 2024 Jacek Olszak | ||
-- This code is licensed under MIT license (see LICENSE for details) | ||
|
||
---@param el {x:number,y:number,width:number,height:number,select:function} | ||
function attach_tree(parent_el, el) | ||
local bg_color <const> = 7 | ||
local fg_color <const> = 13 | ||
local highlight_bg_color <const> = 1 | ||
local highlight_fg_color <const> = 7 | ||
|
||
local node_height <const> = 10 | ||
|
||
local nodes_by_id <const> = {} | ||
|
||
local selected_child | ||
|
||
el = parent_el:attach(el) | ||
|
||
local root_node = el:attach( | ||
{ width = el.width, height = 0, x = 0, y = 0 } | ||
) | ||
root_node.indent = "" | ||
el:attach_scrollbars { autohide = true } | ||
include "gui/tree_provider.lua" | ||
|
||
local provider <const> = new_tree_provider() | ||
|
||
local selected_line = nil | ||
|
||
local tree = attach_text_output(parent_el, { | ||
x = el.x, | ||
y = el.y, | ||
width = el.width, | ||
height = el.height, | ||
bg_color = 7, | ||
get_line = function(line_no) | ||
local node = provider:get_node(line_no) | ||
local whitespace = " " | ||
|
||
local fg_color = 13 | ||
local bg_color = 7 | ||
if selected_line != nil and selected_line == line_no then | ||
fg_color = 7 | ||
bg_color = 1 | ||
end | ||
|
||
function el:add_child(id, text, parent_id) | ||
local parent | ||
if parent_id == nil then | ||
parent = root_node | ||
else | ||
parent = nodes_by_id[parent_id] | ||
end | ||
local prefix = whitespace:rep(node.depth * 2) | ||
if node.has_children then | ||
prefix = prefix .. "[-] " | ||
else | ||
prefix = prefix .. " " | ||
end | ||
|
||
local text = prefix .. node.text | ||
|
||
local child = parent:attach( | ||
{ | ||
width = parent.width, | ||
height = node_height, | ||
x = 0, | ||
y = parent.height | ||
return { | ||
text = text, | ||
bg_color = bg_color, | ||
fg_color = fg_color, | ||
} | ||
) | ||
child.text = text | ||
child.indent = parent.indent .. " " | ||
if parent_id == nil then | ||
child.indent = "" -- root element should not have an indent | ||
end | ||
function child:draw(msg) | ||
if child == selected_child then | ||
pal(bg_color, highlight_bg_color); | ||
pal(fg_color, highlight_fg_color) | ||
end | ||
local prefix = "[-] " | ||
if #child.child == 0 then | ||
prefix = " " | ||
end | ||
rectfill(0, 0, self.width, node_height, bg_color) | ||
print(child.indent .. prefix .. self.text, 0, 1, fg_color) | ||
pal() | ||
end | ||
|
||
function child:click() | ||
selected_child = child | ||
el:select { id = id } | ||
end, | ||
is_link = function(line_no) | ||
return true | ||
end, | ||
link_click = function(line_no) | ||
selected_line = line_no | ||
el.select(provider:get_node(line_no).id) | ||
end, | ||
lines_len = function() | ||
return provider:nodes_len() | ||
end | ||
}) | ||
|
||
local function add_height(parent, h) | ||
parent.height += h | ||
if parent._parent != nil then | ||
add_height(parent._parent, h) | ||
end | ||
end | ||
|
||
add_height(parent, node_height) | ||
|
||
-- use _parent beause parent is already used by Picotron: | ||
child._parent = parent | ||
nodes_by_id[id] = child | ||
child.cursor = "pointer" | ||
end | ||
|
||
function el:draw() | ||
rectfill(0, 0, el.width, el.height, bg_color) | ||
end | ||
|
||
function el:update_child_text(id, text) | ||
nodes_by_id[id].text = text | ||
function tree:add_child(id, text, parent_id) | ||
provider:append_node(parent_id, id, text) | ||
end | ||
|
||
local function child_y_relative_to_root_node(child) | ||
local y = child.y | ||
while child.parent != root_node do | ||
y += child.parent.y | ||
child = child.parent | ||
end | ||
return y | ||
end | ||
|
||
local function scroll_to_child(child) | ||
local scroll_root_node = | ||
-child_y_relative_to_root_node(child) + (el.height / 2) | ||
if scroll_root_node > 0 then | ||
scroll_root_node = 0 | ||
end | ||
local max_scroll = -root_node.height + el.height | ||
if scroll_root_node <= max_scroll then | ||
scroll_root_node = max_scroll | ||
end | ||
|
||
root_node.y = scroll_root_node | ||
function tree:update_child_text(id, text) | ||
provider:update_node_text(id, text) | ||
end | ||
|
||
function el:select_child(id) | ||
selected_child = nodes_by_id[id] | ||
scroll_to_child(selected_child) | ||
function tree:select_child(id) | ||
local line = provider:get_line_no(id) | ||
selected_line = line | ||
tree:scroll_to_line(line) | ||
end | ||
|
||
return el | ||
return tree | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
-- (c) 2024 Jacek Olszak | ||
-- This code is licensed under MIT license (see LICENSE for details) | ||
|
||
--- returns a new instance of data structure holding tree of nodes. This object | ||
--- is used by tree component. The code was extracted from the tree component | ||
--- because the component was to complex (and will be even more complex | ||
--- when tree will have node collapsing and hiding functionality). | ||
function new_tree_provider() | ||
local p = {} | ||
|
||
local nodes_by_line <const> = {} | ||
local nodes_by_id <const> = {} | ||
|
||
function p:nodes_len() | ||
return #nodes_by_line | ||
end | ||
|
||
function p:get_node(line_no) | ||
return nodes_by_line[line_no] | ||
end | ||
|
||
function p:get_line_no(id) | ||
for line_no, node in ipairs(nodes_by_line) do | ||
if node.id == id then | ||
return line_no | ||
end | ||
end | ||
|
||
return nil | ||
end | ||
|
||
function p:append_node(parent_id, id, text) | ||
local node = { text = text, depth = 0, id = id, has_children = false } | ||
if parent_id != nil then | ||
local parent = nodes_by_id[parent_id] | ||
parent.has_children = true | ||
node.depth = parent.depth + 1 | ||
end | ||
nodes_by_id[node.id] = node | ||
table.insert(nodes_by_line, node) | ||
end | ||
|
||
function p:update_node_text(id, new_text) | ||
nodes_by_id[id].text = new_text | ||
end | ||
|
||
return p | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
-- (c) 2024 Jacek Olszak | ||
-- This code is licensed under MIT license (see LICENSE for details) | ||
|
||
include "tree_provider.lua" | ||
|
||
test("new provider has 0 lines", function() | ||
local p = new_tree_provider() | ||
assert_eq(0, p:nodes_len()) | ||
end) | ||
|
||
test("add root", function() | ||
local p = new_tree_provider() | ||
p:append_node(nil, 1, "root") | ||
assert_eq(1, p:nodes_len()) | ||
assert_eq({ text = "root", depth = 0, id = 1, has_children = false }, p:get_node(1)) | ||
assert_eq(1, p:get_line_no(1)) | ||
end) | ||
|
||
test("add child node", function() | ||
local p = new_tree_provider() | ||
p:append_node(nil, 1, "root") | ||
-- when | ||
p:append_node(1, 2, "child") | ||
-- then | ||
assert_eq(2, p:nodes_len()) | ||
assert_eq({ text = "child", depth = 1, id = 2, has_children = false }, p:get_node(2)) | ||
assert_eq(2, p:get_line_no(2)) | ||
-- and | ||
assert(p:get_node(1).has_children) | ||
end) | ||
|
||
test("update node text", function() | ||
local p = new_tree_provider() | ||
p:append_node(nil, 1, "root") | ||
-- when | ||
p:update_node_text(1, "updated") | ||
-- then | ||
assert_eq("updated", p:get_node(1).text) | ||
end) |