perf(ui): build nodes without per-node marshalling tables
build() copied a node's children into a second table so it could nil them out of the spec, and applyStyle() copied the style keys into a third. Both existed only to hand C a table it was already holding: tree.create reads named fields and never touches the array part, so children come straight off the spec, and create now applies the style itself. That is one C call per styled node instead of two, and the sparse-style decision is made by the set mask rather than by a loop over key names in Lua. Measured in the emulator, 12 sensors / 131 nodes: build 106ms -> 74ms, Lua transient +18.1kB -> +12.0kB, free heap at build end 18572 -> 27936. Tree footprint is byte-identical, so the same styles are applied. ui.lua stripped bytecode 10024 -> 9621 bytes, saved in every app state. An explicit fill now wins over background, where the Lua version had background clobber it; no caller sets both.
This commit is contained in:
+4
-36
@@ -163,34 +163,6 @@ tree.setPainter(function(id, x, y, w, h, clipX, clipY, clipW, clipH)
|
||||
end
|
||||
end)
|
||||
|
||||
local STYLE_KEYS = {
|
||||
"color",
|
||||
"fill",
|
||||
"border",
|
||||
"face",
|
||||
"pressedFace",
|
||||
"pressedColor",
|
||||
"focusColor",
|
||||
"radius",
|
||||
"font",
|
||||
"textStyle",
|
||||
}
|
||||
|
||||
local function applyStyle(id, spec)
|
||||
local style, hasStyle = {}, false
|
||||
for _, key in ipairs(STYLE_KEYS) do
|
||||
if spec[key] ~= nil then
|
||||
style[key], hasStyle = spec[key], true
|
||||
end
|
||||
end
|
||||
if spec.background ~= nil then
|
||||
style.background, style.fill, hasStyle = spec.background, spec.background, true
|
||||
end
|
||||
if hasStyle then
|
||||
tree.setStyle(id, style)
|
||||
end
|
||||
end
|
||||
|
||||
local function build(spec, kind)
|
||||
spec = spec or {}
|
||||
-- Nodes outside a build would reset the arena under the screen already on the
|
||||
@@ -199,20 +171,16 @@ local function build(spec, kind)
|
||||
error("build nodes from the function ui.mount() was given, then ui.rebuild()", 3)
|
||||
end
|
||||
|
||||
local children = {}
|
||||
for index, child in ipairs(spec) do
|
||||
children[index] = child
|
||||
spec[index] = nil
|
||||
end
|
||||
|
||||
spec.type = kind
|
||||
spec.interactive = spec.on_enter ~= nil or spec.on_exit ~= nil or spec.on_click ~= nil
|
||||
-- Children come straight off the spec's array part. tree.create reads only named
|
||||
-- fields, so copying them into a second table cost one extra Lua table per node
|
||||
-- -- roughly half again the spec's own cost, on the build path that peaks the heap.
|
||||
local id = tree.create(nil, spec)
|
||||
for _, child in ipairs(children) do
|
||||
for _, child in ipairs(spec) do
|
||||
tree.attach(id, child)
|
||||
end
|
||||
|
||||
applyStyle(id, spec)
|
||||
enterHandlers[id] = spec.on_enter
|
||||
exitHandlers[id] = spec.on_exit
|
||||
clickHandlers[id] = spec.on_click
|
||||
|
||||
@@ -144,6 +144,50 @@ int reset(lua_State* state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void readStyleColor(lua_State* state, int index, const char* key,
|
||||
int32_t& field, uint16_t flag, uint16_t& set) {
|
||||
lua_getfield(state, index, key);
|
||||
if (!lua_isnoneornil(state, -1)) {
|
||||
field = static_cast<int32_t>(luaL_checkinteger(state, -1));
|
||||
set |= flag;
|
||||
}
|
||||
lua_pop(state, 1);
|
||||
}
|
||||
|
||||
// Reads whatever style fields a spec carries, returning whether it named any. The
|
||||
// Lua wrapper used to do this by copying the style keys into a second table and
|
||||
// mapping background itself, which cost a table per styled node and a rehash of the
|
||||
// caller's spec to express the mapping.
|
||||
bool readStyleInto(lua_State* state, int index, ui::Style& style) {
|
||||
const uint16_t before = style.set;
|
||||
readStyleColor(state, index, "color", style.color, ui::S_COLOR, style.set);
|
||||
readStyleColor(state, index, "background", style.bg, ui::S_BG, style.set);
|
||||
readStyleColor(state, index, "fill", style.fill, ui::S_FILL, style.set);
|
||||
readStyleColor(state, index, "border", style.border, ui::S_BORDER, style.set);
|
||||
readStyleColor(state, index, "face", style.face, ui::S_FACE, style.set);
|
||||
readStyleColor(state, index, "pressedFace", style.pressedFace,
|
||||
ui::S_PRESSED_FACE, style.set);
|
||||
readStyleColor(state, index, "pressedColor", style.pressedColor,
|
||||
ui::S_PRESSED_COLOR, style.set);
|
||||
readStyleColor(state, index, "focusColor", style.focusColor,
|
||||
ui::S_FOCUS_COLOR, style.set);
|
||||
readStyleColor(state, index, "font", style.font, ui::S_FONT, style.set);
|
||||
readStyleColor(state, index, "textStyle", style.textStyle, ui::S_TEXT_STYLE,
|
||||
style.set);
|
||||
|
||||
int32_t radius = style.radius;
|
||||
readStyleColor(state, index, "radius", radius, ui::S_RADIUS, style.set);
|
||||
style.radius = static_cast<uint8_t>(radius);
|
||||
|
||||
// A background is the surface its own corners blend into, so it implies fill.
|
||||
// An explicit fill wins, which is the way round the Lua version had it backwards.
|
||||
if ((style.set & ui::S_BG) && !(style.set & ui::S_FILL)) {
|
||||
style.fill = style.bg;
|
||||
style.set |= ui::S_FILL;
|
||||
}
|
||||
return style.set != before;
|
||||
}
|
||||
|
||||
int create(lua_State* state) {
|
||||
const bool hasParent = !lua_isnoneornil(state, 1);
|
||||
const uint16_t parent = hasParent ? checkNode(state, 1) : ui::NONE;
|
||||
@@ -199,6 +243,14 @@ int create(lua_State* state) {
|
||||
if (label)
|
||||
tree(state).setLabel(id, label);
|
||||
lua_pop(state, 1);
|
||||
|
||||
// Styled here rather than by a second call from Lua: the spec is already on the
|
||||
// stack, and a node naming no colours must cost no Style entry, which is a
|
||||
// decision the set mask makes better than a loop over key names in Lua.
|
||||
ui::Style style;
|
||||
if (readStyleInto(state, 2, style))
|
||||
tree(state).styleFor(id) = style;
|
||||
|
||||
lua_pushinteger(state, id);
|
||||
return 1;
|
||||
}
|
||||
@@ -316,39 +368,11 @@ int getParent(lua_State* state) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void readStyleColor(lua_State* state, int index, const char* key,
|
||||
int32_t& field, uint16_t flag, uint16_t& set) {
|
||||
lua_getfield(state, index, key);
|
||||
if (!lua_isnoneornil(state, -1)) {
|
||||
field = static_cast<int32_t>(luaL_checkinteger(state, -1));
|
||||
set |= flag;
|
||||
}
|
||||
lua_pop(state, 1);
|
||||
}
|
||||
|
||||
int setStyle(lua_State* state) {
|
||||
const uint16_t id = checkNode(state, 1);
|
||||
luaL_checktype(state, 2, LUA_TTABLE);
|
||||
|
||||
ui::Style& style = tree(state).styleFor(id);
|
||||
readStyleColor(state, 2, "color", style.color, ui::S_COLOR, style.set);
|
||||
readStyleColor(state, 2, "background", style.bg, ui::S_BG, style.set);
|
||||
readStyleColor(state, 2, "fill", style.fill, ui::S_FILL, style.set);
|
||||
readStyleColor(state, 2, "border", style.border, ui::S_BORDER, style.set);
|
||||
readStyleColor(state, 2, "face", style.face, ui::S_FACE, style.set);
|
||||
readStyleColor(state, 2, "pressedFace", style.pressedFace, ui::S_PRESSED_FACE,
|
||||
style.set);
|
||||
readStyleColor(state, 2, "pressedColor", style.pressedColor,
|
||||
ui::S_PRESSED_COLOR, style.set);
|
||||
readStyleColor(state, 2, "focusColor", style.focusColor, ui::S_FOCUS_COLOR,
|
||||
style.set);
|
||||
readStyleColor(state, 2, "font", style.font, ui::S_FONT, style.set);
|
||||
readStyleColor(state, 2, "textStyle", style.textStyle, ui::S_TEXT_STYLE,
|
||||
style.set);
|
||||
|
||||
int32_t radius = style.radius;
|
||||
readStyleColor(state, 2, "radius", radius, ui::S_RADIUS, style.set);
|
||||
style.radius = static_cast<uint8_t>(radius);
|
||||
readStyleInto(state, 2, tree(state).styleFor(id));
|
||||
|
||||
tree(state).nodes[id].flags |= ui::DIRTY;
|
||||
return 0;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user