perf(#306): batch per-contact setting loads instead of notifying 600 times

Diagnosed from the app's own log. After SELF_INFO the client went completely
silent for 43.7s, then flushed a burst of radio frames all sharing the
identical timestamp - queued while the event loop was blocked, not a slow
radio.

Cause: loadContactCache() looped over every cached contact calling
_ensureContactSmazSettingLoaded and _ensureContactCyr2LatSettingLoaded, and
each of those notified on completion. With 300 contacts that is 600
notifyListeners() calls in a burst, each rebuilding the whole widget tree, and
each rebuild itself walks the contact list. O(contacts^2) on the UI isolate.

Both loaders are now awaitable and take notify: false for bulk use.
loadContactCache awaits them all and notifies once.

Measured on the reporting device (radio 4f6585264e, TCP): 300 contacts, 1090
discovered contacts, 12 channels holding 1841 messages, 6.9 MB prefs file.
Log evidence: 43.7s stall between "Pulled battery" and the first channel
response, followed by same-millisecond frame delivery.

This is the Windows-visible freeze in #306. The platform asymmetry is
consistent with contact-list size rather than anything desktop-specific, so
whether Android is genuinely faster or simply has a smaller address book on
its paired radio is still open - #306 stays open pending a like-for-like
re-measure.

flutter analyze clean, dart format clean, 469 tests pass.
integration/290-306-335
Strycher 2 days ago
parent 2c80be3244
commit 7f9f6ba63b

@ -1191,10 +1191,17 @@ class MeshCoreConnector extends ChangeNotifier {
_contacts _contacts
..clear() ..clear()
..addAll(cached); ..addAll(cached);
for (final contact in cached) { // Load every contact's settings without notifying per contact, then
_ensureContactSmazSettingLoaded(contact.publicKeyHex); // notify once. Per-contact notification rebuilt the whole tree 2x per
_ensureContactCyr2LatSettingLoaded(contact.publicKeyHex); // contact (600 rebuilds for 300 contacts), and each rebuild walks the
} // contact list, which is what stalled the UI for ~44s on connect.
await Future.wait([
for (final contact in cached) ...[
_ensureContactSmazSettingLoaded(contact.publicKeyHex, notify: false),
_ensureContactCyr2LatSettingLoaded(contact.publicKeyHex, notify: false),
],
]);
notifyListeners();
} }
Future<void> _loadDiscoveredContactCache() async { Future<void> _loadDiscoveredContactCache() async {
@ -5625,22 +5632,32 @@ class MeshCoreConnector extends ChangeNotifier {
return frame.sublist(prefixOffset, prefixOffset + prefixLen); return frame.sublist(prefixOffset, prefixOffset + prefixLen);
} }
void _ensureContactSmazSettingLoaded(String contactKeyHex) { /// [notify] is false for bulk loads, which notify once at the end instead.
/// Notifying per contact rebuilds the whole tree once per contact, and each
/// rebuild itself walks the contact list, so a large address book turns this
/// into O(contacts^2) work on the UI isolate.
Future<void> _ensureContactSmazSettingLoaded(
String contactKeyHex, {
bool notify = true,
}) async {
if (_contactSmazEnabled.containsKey(contactKeyHex)) return; if (_contactSmazEnabled.containsKey(contactKeyHex)) return;
_contactSettingsStore.loadSmazEnabled(contactKeyHex).then((enabled) { final enabled = await _contactSettingsStore.loadSmazEnabled(contactKeyHex);
if (_contactSmazEnabled[contactKeyHex] == enabled) return; if (_contactSmazEnabled[contactKeyHex] == enabled) return;
_contactSmazEnabled[contactKeyHex] = enabled; _contactSmazEnabled[contactKeyHex] = enabled;
notifyListeners(); if (notify) notifyListeners();
});
} }
void _ensureContactCyr2LatSettingLoaded(String contactKeyHex) { Future<void> _ensureContactCyr2LatSettingLoaded(
String contactKeyHex, {
bool notify = true,
}) async {
if (_contactCyr2LatEnabled.containsKey(contactKeyHex)) return; if (_contactCyr2LatEnabled.containsKey(contactKeyHex)) return;
_contactSettingsStore.loadCyr2LatEnabled(contactKeyHex).then((enabled) { final enabled = await _contactSettingsStore.loadCyr2LatEnabled(
if (_contactCyr2LatEnabled[contactKeyHex] == enabled) return; contactKeyHex,
_contactCyr2LatEnabled[contactKeyHex] = enabled; );
notifyListeners(); if (_contactCyr2LatEnabled[contactKeyHex] == enabled) return;
}); _contactCyr2LatEnabled[contactKeyHex] = enabled;
if (notify) notifyListeners();
} }
void _ensureContactCyr2LatProfileLoaded(String contactKeyHex) { void _ensureContactCyr2LatProfileLoaded(String contactKeyHex) {

Loading…
Cancel
Save

Powered by TurnKey Linux.