Skip to content

perf(dashboard): build the Urban, Grid and Deck render key without a temporary list - #787

Merged
heliflieger merged 2 commits into
rotorflight:masterfrom
flugifix:pr/dashboard-renderkey-concat
Oct 18, 2026
Merged

heliflieger merged 2 commits into
rotorflight:masterfrom
flugifix:pr/dashboard-renderkey-concat

Conversation

@flugifix

Copy link
Copy Markdown
Collaborator

What and why

Closes #765

L.renderKey in widgets/dashboard/themes/urban/layout.lua, grid/layout.lua and deck/layout.lua runs every 0.5 s in the host's state pass and returned table.concat({ "urban", tostring(zone.w), ... }, "|"), although the comment above it said the function makes no table allocation. The table constructor was most of what a call cost. The code is mine; Grid and Deck carry it as copies of Urban's.

The key is now one .. expression with the same terms, in the same order, with the same separator. The diversity term (L.diversity(state) and "div" or "") is parenthesised, because .. binds tighter than and/or; every other term already is a tostring(...) call. The comment now says what a call still allocates: the key string itself. In practice that is on every call, because the key always carries a model picture's path (the snapshot falls back to the suite logo's when no picture is found), which takes it over Lua 5.3's 40-byte short-string limit, so the key is a new string each time on master and on this branch alike. The issue's reproduction uses an empty derived and therefore a short key; with the logo's path in the key, master allocates about 319 bytes per call, not 201.

What it costs, master 1b8d789bf against this branch

The issue's reproduction (Lua 5.3.6, 64-bit desktop, bin/accounting's stubs, unchanged inputs, collector stopped, 1000 calls), extended with instructions per call (debug.sethook(..., "", 1), as bin/accounting counts) and two more inputs: derived.model_image set to the suite logo's path, and to a picture path with Deck's lqSource set. Both trees in the same run, each run twice in opposite theme order; the two runs agree.

call key length bytes per call, master / branch instructions per call, master / branch
control: empty function -- 0 / 0 4 / 4
control: a table of eight numbers -- 184 / 184 14 / 14
Urban, the issue's inputs 35 201 / 1 113 / 116
Urban, the suite logo's path 93 319 / 119 112 / 115
Grid, the issue's inputs 34 201 / 1 113 / 116
Grid, the suite logo's path 92 318 / 118 112 / 115
Deck, the issue's inputs 35 217 / 1 119 / 123
Deck, the suite logo's path 93 335 / 119 118 / 122
Deck, a picture path and lqSource 77 319 / 103 117 / 121

The saving is the table, exactly: 200 bytes per call for Urban and Grid (nine slots), 216 for Deck (ten), whatever the key holds. What is left is the key string. The count hook bills three more instructions per call (four for Deck): one constant load per separator replaces the table constructor and the call to table.concat, whose own work in C (its buffer and the copies) the hook does not count. At two calls a second that is 6 to 8 instructions a second against about 400 to 430 bytes a second less garbage.

lua5.3 bin/accounting/measure.lua, master against this branch: the output is identical line for line (142 rows, 0 failures on both), so no row moved. Its passes do not reach renderKey, which the host calls at most every 0.5 s.

Nothing was run on a radio for these figures; a radio's Lua is 32-bit, so the bytes are smaller there.

What does not change

The key, for every input. Compared in one run between master and this branch, 31 inputs for each of the three themes (93 keys): each term changed on its own (zone width, also as a float, and height; the cell count, also through themeConfig.v_max and with 0; the model picture, short and long; diversity as true, 2, 0 and false; themePhase, and flightMode as its fallback; the transmitter voltage moved, moved back, moved inside its 0.15 V dead band and nil; the battery pick pending, not a table and nil; Deck's lqSource), plus unrelated telemetry, state nil, zone nil, both nil and state = {}. 0 of 93 keys differ. On both trees every term change moves the key, and the dead-band move, the unrelated telemetry and the armed mode under an unchanged themePhase do not. A copy whose last separator is / differs in 93 of 93; a copy that drops Urban's pending term or Deck's lqSource term is caught by the term check on exactly those two inputs.

On the EdgeTX 2.12.2 simulator (TX16S MK3, 800x480) against a Rotorflight 4.6.0 SITL flight controller, with a picture under /IMAGES/ named after the board's craft name, the dashboard with this branch connects, builds Urban, Grid and Deck and draws the picture in each, with the same number of theme builds per run as master (two in every run). Master and this branch differ only in the clock and, on Urban and Grid, in the arming-disabled reason in the status bar, which rotates every two seconds; two runs of master differ there as well. Urban, Grid and Deck differ from each other in more than 129 000 pixels, so the comparison sees which theme drew. No run logged a Lua error.

Related: #755 and #756, which brought Grid and Deck with their copies of the function. #763 and #764 (merged as #775) are the other allocations on the same state pass. #777 changes the same three layout.lua files elsewhere (the statistics screens); the two merge without a conflict (checked against its current head).

Checklist

See CONTRIBUTING.md for each rule.

  • A release note is added under release-notes/, or a line below says why not.
  • docs/ is updated, or a line below says why no page changes.
  • Every string a pilot reads goes through i18n.

Documentation: no page -- nothing a pilot can observe changes; the key, and so every rebuild, is identical for every input, and no page under docs/ describes what the render key allocates.

…temporary list

L.renderKey runs every 0.5 s in the host's state pass and joined its terms with
table.concat({ ... }, "|"), although the comment above it said no table was
allocated. The table constructor was most of what a call cost: 201 bytes per
call for Urban and Grid, 217 for Deck, on desktop Lua 5.3 with unchanged inputs.

The key is now one `..` expression with the same terms, the same order and the
same separator; the diversity term is parenthesised because `..` binds tighter
than `and`/`or`. A call with unchanged inputs now allocates 1 byte, and 200 or
216 bytes less with a long model-image path. The key is byte-identical for
every input, and the comment says what the function still allocates.

Closes rotorflight#765
The key always carries a model picture's path -- the snapshot falls back to
the suite logo when no picture is found -- so it is longer than Lua 5.3's
40-byte short-string limit and is a new string on every call. Say that in
the comment instead of "nothing", and quote the saving in the release note
for that case: 200 bytes less per call in Urban and Grid, 216 in Deck.
@flugifix
flugifix requested a review from heliflieger October 18, 2026 06:25
@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: rotorflight/rotorflight-lua-edgetx-suite/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 05ae7085-ec3d-4d38-8056-bcc09c912492
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@heliflieger heliflieger left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three copies carry the same terms, in the same order, with the same separator, and a key comparison over 31 inputs per theme is the right check for a change whose whole point is that nothing observable moves -- including the controls that must differ. The comment above the function now says what a call still allocates (the key string) instead of claiming no allocation, which is the part the issue was really about. Approving.

.. "|" .. (L.diversity(state) and "div" or "")
-- The phase, and it is `themePhase` rather than `flightMode` on purpose.
--
-- A host that knows the two refinement phases puts the widget into `armed` on the arm edge

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parentheses are load-bearing and correct: .. binds tighter than and/or, so .. L.diversity(state) and "div" or "" would have concatenated the boolean instead of the string. Every other term is an explicit tostring(...), so no number's rendering can change either way.

@heliflieger
heliflieger merged commit f6ca12e into rotorflight:master Oct 18, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard: Urban, Grid and Deck build their render key from a temporary table, although its comment says no table is allocated

2 participants