perf(l2): long lists build only the rows on screen

New message, Nodes (and its Scan popup), Admin and Messages show their
items through one virtual list: a screenful of rows, handed to the next
item as one scrolls out, the scroll extent from the items' heights. 256
contacts: 522 objects and ~950 ms of filling in (frames up to 96 ms)
before, 19 objects at once now; a scrolled frame 28.5 -> 11.7 ms. Nodes
257 -> 49 objects. A new message rewrites Messages' rows in place
(46 -> 20 ms) instead of building the screen again; it's rebuilt only
when "Read all" comes or goes. A held row keeps what it shows until let
go. Perf tools: -D PERF_LISTS, -D PERF_LIST_MUL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-01 20:56:14 +02:00
co-authored by Claude Opus 5.5
parent 71e3dd8a8b
commit c8e1f4a604
7 changed files with 531 additions and 158 deletions
+22 -16
View File
@@ -63,7 +63,7 @@ static void onAdminPwKb(lv_event_t* e) { s_ui->adminLogin(lv_event_get_code(e) =
static void onAdminTextKb(lv_event_t* e) { s_ui->adminTextDone(lv_event_get_code(e) == LV_EVENT_READY); }
static void onAdminCancelWait(lv_event_t* e) { (void)e; s_ui->adminCancelWait(); }
static void onOpenAdminPick(lv_event_t* e) { (void)e; s_ui->showAdminPick(); }
static void onAdminPickRow(lv_event_t* e) { s_ui->adminPick((int)(uintptr_t)lv_event_get_user_data(e)); }
static void onAdminPickRow(lv_event_t* e) { s_ui->adminPick((int)vlist::arg(e)); }
namespace adminview {
static const int PICK_MAX = 128;
@@ -80,6 +80,8 @@ void UITask::showAdminPick() {
void UITask::buildAdminPick() {
lv_obj_t* body = newScreen("Admin", true);
vlist::attach(body);
vlist::begin(body);
int rows = 0;
for (int pass = 0; pass < 2; pass++) { // favourites first
for (int i = 0; i < the_mesh.getNumContacts() && rows < adminview::PICK_MAX; i++) {
@@ -92,23 +94,27 @@ void UITask::buildAdminPick() {
rows++;
}
}
if (rows) sectionTitle(body, "LOG IN TO");
fillStart(rows, 8, &UITask::adminPickRow); // the rest as the loop goes (fill)
if (rows) vlist::own(body, sectionTitle(body, "LOG IN TO"));
static const vlist::Kind ROW = {
theme::ROW_H,
[](lv_obj_t* list) { return vRow(list, onAdminPickRow, NULL); },
[](lv_obj_t* row, intptr_t 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) {
rowSet(row, "?", NULL); // deleted meanwhile
return;
}
char name[48];
snprintf(name, sizeof(name), "%s%s", contactctl::favourite(c) ? UI_SYMBOL_STAR " " : "", c.name);
rowSet(row, name, c.type == ADV_TYPE_ROOM ? "Room server" : "Repeater");
},
};
for (int i = 0; i < rows; i++) vlist::add(body, &ROW, i);
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);
vlist::own(body, 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);
vlist::end(body);
}
void UITask::adminPick(int row) {