perf(l2): heavy screens build in pieces over several loop passes

The list fill becomes a small job queue (fill/then/fillStart, fillTick with a
25 ms budget per pass). Settings pages build section by section in
schemaRows, with the trailing extras (melodies, prune, quick messages) queued
after them so the order stays; Radio and Repeater build by group, Admin's
repeater pick like the contact list, Diag's Live tab by section. Rebuilds
that restore the scroll flush the queue first. Screens went from 220-360 ms
to 80-175 ms (build + first frame).

Diag's crash line read the core dump in flash every second (~44 ms each);
it is read once now. The perf run times every page and dismisses the USB
popup, which had covered all earlier measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-30 08:53:15 +02:00
co-authored by Claude Opus 5.5
parent 34f739c45b
commit 039485bf34
8 changed files with 205 additions and 81 deletions
+17 -7
View File
@@ -68,6 +68,7 @@ static void onAdminPickRow(lv_event_t* e) { s_ui->adminPick((int)(uintptr_t)lv_
namespace adminview {
static const int PICK_MAX = 128;
static uint8_t (*s_pick)[PUB_KEY_SIZE] = psramBuf<uint8_t[PUB_KEY_SIZE]>(PICK_MAX);
static uint16_t* s_pick_raw = psramBuf<uint16_t>(PICK_MAX); // their raw table index, for the row
}
// Home > Admin: every repeater and room server, favourites first (ui-new's
@@ -80,27 +81,36 @@ void UITask::showAdminPick() {
void UITask::buildAdminPick() {
lv_obj_t* body = newScreen("Admin", true);
int rows = 0;
for (int pass = 0; pass < 2; pass++) {
for (int pass = 0; pass < 2; pass++) { // favourites first
for (int i = 0; i < the_mesh.getNumContacts() && rows < adminview::PICK_MAX; i++) {
ContactInfo c;
if (!the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c)) continue;
if (c.type != ADV_TYPE_REPEATER && c.type != ADV_TYPE_ROOM) continue;
bool fav = contactctl::favourite(c);
if (fav != (pass == 0)) continue;
if (!rows) sectionTitle(body, "LOG IN TO");
char name[48];
snprintf(name, sizeof(name), "%s%s", fav ? UI_SYMBOL_STAR " " : "", c.name);
if (contactctl::favourite(c) != (pass == 0)) continue;
memcpy(adminview::s_pick[rows], c.id.pub_key, PUB_KEY_SIZE);
listRow(body, name, c.type == ADV_TYPE_ROOM ? "Room server" : "Repeater", onAdminPickRow, (void*)(uintptr_t)rows);
adminview::s_pick_raw[rows] = (uint16_t)(MAX_ANON_CONTACTS + i);
rows++;
}
}
if (rows) sectionTitle(body, "LOG IN TO");
fillStart(rows, 8, &UITask::adminPickRow); // the rest as the loop goes (fill)
if (!rows) {
noteLabel(body, "No repeaters or room servers yet. They show up here once their advert is heard.",
THEME_FONT_BODY, theme::TEXT_MUTED);
}
}
void UITask::adminPickRow(int i) {
ContactInfo c;
if (!the_mesh.getContactByIdx(adminview::s_pick_raw[i], c) || memcmp(c.id.pub_key, adminview::s_pick[i], PUB_KEY_SIZE) != 0) {
listRow(_body, "?", NULL, onAdminPickRow, (void*)(uintptr_t)i); // deleted meanwhile
return;
}
char name[48];
snprintf(name, sizeof(name), "%s%s", contactctl::favourite(c) ? UI_SYMBOL_STAR " " : "", c.name);
listRow(_body, name, c.type == ADV_TYPE_ROOM ? "Room server" : "Repeater", onAdminPickRow, (void*)(uintptr_t)i);
}
void UITask::adminPick(int row) {
if (row < 0 || row >= adminview::PICK_MAX) return;
openAdmin(adminview::s_pick[row]);